Module: BSV::Primitives::EncryptedMessage

Defined in:
lib/bsv/primitives/encrypted_message.rb

Overview

BRC-78 encrypted messages.

Provides confidential authenticated messaging using BRC-42 derived keys and AES-256-GCM symmetric encryption. Both parties derive the same symmetric key from their respective BRC-42 child keys via ECDH.

Examples:

Encrypt and decrypt

ct = EncryptedMessage.encrypt(message, sender_priv, recipient_pub)
EncryptedMessage.decrypt(ct, recipient_priv) #=> message

See Also:

Constant Summary collapse

VERSION =

Protocol version bytes: “BBx10x33”

"\x42\x42\x10\x33".b.freeze
HEADER_SIZE =

Minimum message size: VERSION(4) + sender(33) + recipient(33) + key_id(32) = 102

102

Class Method Summary collapse

Class Method Details

.decrypt(data, recipient) ⇒ String

Decrypt a BRC-78 encrypted message.

Parameters:

  • data (String)

    the binary encrypted message (from encrypt)

  • recipient (PrivateKey)

    the recipient’s private key

Returns:

  • (String)

    the decrypted plaintext (binary)

Raises:

  • (ArgumentError)

    if version is wrong, recipient doesn’t match, or message is too short

  • (OpenSSL::Cipher::CipherError)

    if decryption fails (tampered or wrong key)



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/bsv/primitives/encrypted_message.rb', line 54

def decrypt(data, recipient)
  data = data.b
  raise ArgumentError, "encrypted message too short: #{data.bytesize} bytes (minimum #{HEADER_SIZE})" if data.bytesize < HEADER_SIZE

  version = data.byteslice(0, 4)
  raise ArgumentError, "message version mismatch: expected #{VERSION.unpack1('H*')}, received #{version.unpack1('H*')}" if version != VERSION

  sender_pub = PublicKey.from_bytes(data.byteslice(4, 33))
  expected_recipient = data.byteslice(37, 33)
  actual_recipient = recipient.public_key.compressed

  if expected_recipient != actual_recipient
    raise ArgumentError,
          "the encrypted message expects a recipient public key of #{expected_recipient.unpack1('H*')}, " \
          "but the provided key is #{actual_recipient.unpack1('H*')}"
  end

  key_id = data.byteslice(70, 32)
  encrypted_payload = data.byteslice(HEADER_SIZE, data.bytesize - HEADER_SIZE)

  invoice = "2-message encryption-#{[key_id].pack('m0')}"
  sym_key = derive_symmetric_key_for_decrypt(sender_pub, recipient, invoice)
  sym_key.decrypt(encrypted_payload)
end

.encrypt(message, sender, recipient) ⇒ String

Encrypt a message using the BRC-78 protocol.

Parameters:

  • message (String)

    the message to encrypt

  • sender (PrivateKey)

    the sender’s private key

  • recipient (PublicKey)

    the recipient’s public key

Returns:

  • (String)

    binary encrypted message (header + AES-GCM payload)



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/bsv/primitives/encrypted_message.rb', line 33

def encrypt(message, sender, recipient)
  key_id = SecureRandom.random_bytes(32)
  invoice = "2-message encryption-#{[key_id].pack('m0')}"

  sym_key = derive_symmetric_key(sender, recipient, invoice)
  encrypted = sym_key.encrypt(message)

  VERSION +
    sender.public_key.compressed +
    recipient.compressed +
    key_id +
    encrypted
end