Module: Solana::Ruby::Kit::OffchainMessages::Codec

Extended by:
T::Sig
Included in:
Solana::Ruby::Kit::OffchainMessages
Defined in:
lib/solana/ruby/kit/offchain_messages/codec.rb

Overview

Wire format for off-chain messages (Solana standard).

v0: [0xFF, 0xFF] + domain_len(u8) + domain + version(u8) + message_len(u16LE) + message v1: [0xFF, 0xFF] + domain_len(u8) + domain + version(u8) + app_domain_len(u16LE)

+ app_domain + message_len(u16LE) + message

Constant Summary collapse

MAGIC =
T.let("\xFF\xFF".b.freeze, String)

Class Method Summary collapse

Class Method Details

.decode_offchain_message(bytes) ⇒ Object

rubocop:disable Metrics/MethodLength



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/solana/ruby/kit/offchain_messages/codec.rb', line 49

def decode_offchain_message(bytes) # rubocop:disable Metrics/MethodLength
  b = bytes.b
  Kernel.raise SolanaError.new(SolanaError::OFFCHAIN_MESSAGES__INVALID_MESSAGE_FORMAT) unless b.byteslice(0, 2) == MAGIC

  offset     = 2
  domain_len = b.byteslice(offset, 1)&.unpack1('C') || 0
  offset    += 1
  domain     = b.byteslice(offset, domain_len)&.force_encoding('ASCII') || ''
  offset    += domain_len
  version    = b.byteslice(offset, 1)&.unpack1('C') || 0
  offset    += 1

  application_domain = nil
  if version >= 1
    app_len = b.byteslice(offset, 2)&.unpack1('v') || 0
    offset += 2
    application_domain = b.byteslice(offset, app_len)&.force_encoding('ASCII')
    offset += app_len
  end

  msg_len = b.byteslice(offset, 2)&.unpack1('v') || 0
  offset += 2
  message = b.byteslice(offset, msg_len)&.force_encoding('UTF-8') || ''

  Message.new(
    version:            version,
    domain:             domain,
    message:            message,
    application_domain: application_domain
  )
end

.encode_offchain_message(msg) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/solana/ruby/kit/offchain_messages/codec.rb', line 22

def encode_offchain_message(msg)
  domain_b = msg.domain.encode('ASCII').b
  Kernel.raise ArgumentError, 'Domain exceeds 255 bytes' if domain_b.bytesize > 255

  message_b = msg.message.encode('UTF-8').b
  Kernel.raise ArgumentError, 'Message too large' if message_b.bytesize > 0xFFFF

  buf = MAGIC.dup
  buf << [domain_b.bytesize].pack('C')
  buf << domain_b
  buf << [msg.version].pack('C')

  if msg.version >= 1 && msg.application_domain
    app_b = T.must(msg.application_domain).encode('ASCII').b
    Kernel.raise ArgumentError, 'Application domain exceeds 65535 bytes' if app_b.bytesize > 0xFFFF

    buf << [app_b.bytesize].pack('v')
    buf << app_b
  end

  buf << [message_b.bytesize].pack('v')
  buf << message_b
  buf.b
end

.sign_offchain_message(signer, msg) ⇒ Object



83
84
85
86
# File 'lib/solana/ruby/kit/offchain_messages/codec.rb', line 83

def sign_offchain_message(signer, msg)
  payload = encode_offchain_message(msg)
  Keys.encode_signature(signer.sign(payload))
end

.verify_offchain_message_signature(verify_key, signature, msg) ⇒ Object



96
97
98
99
100
101
102
103
104
# File 'lib/solana/ruby/kit/offchain_messages/codec.rb', line 96

def verify_offchain_message_signature(verify_key, signature, msg)
  payload  = encode_offchain_message(msg)
  vk       = RbNaCl::VerifyKey.new(verify_key)
  sig_bytes = signature.respond_to?(:to_bytes) ? signature.to_s : [signature.value].pack('H*')
  vk.verify(sig_bytes, payload)
  true
rescue RbNaCl::BadSignatureError
  false
end