Module: BSV::Primitives::SignedMessage

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

Overview

BRC-77 signed messages.

Provides authenticated messaging using BRC-42 derived signing keys. The sender proves their identity to a specific recipient (or anyone) without encrypting the message content.

Examples:

Sign and verify for a specific recipient

sig = SignedMessage.sign(message, sender_priv, recipient_pub)
SignedMessage.verify(message, sig, recipient_priv) #=> true

Sign for anyone to verify

sig = SignedMessage.sign(message, sender_priv)
SignedMessage.verify(message, sig) #=> true

See Also:

Constant Summary collapse

VERSION =

Protocol version bytes: “BB3x01”

"\x42\x42\x33\x01".b.freeze

Class Method Summary collapse

Class Method Details

.sign(message, signer, verifier = nil) ⇒ String

Sign a message using the BRC-77 protocol.

Parameters:

  • message (String)

    the message to sign

  • signer (PrivateKey)

    the sender’s private key

  • verifier (PublicKey, nil) (defaults to: nil)

    the recipient’s public key (nil for anyone-can-verify)

Returns:

  • (String)

    binary signed message (version + keys + key_id + DER signature)



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/bsv/primitives/signed_message.rb', line 34

def sign(message, signer, verifier = nil)
  anyone = verifier.nil?
  verifier = PrivateKey.new(OpenSSL::BN.new(1)).public_key if anyone

  key_id = SecureRandom.random_bytes(32)
  invoice = "2-message signing-#{[key_id].pack('m0')}"

  signing_key = signer.derive_child(verifier, invoice)
  hash = Digest.sha256(message.b)
  signature = signing_key.sign(hash)

  VERSION +
    signer.public_key.compressed +
    (anyone ? "\x00".b : verifier.compressed) +
    key_id +
    signature.to_der
end

.verify(message, sig, recipient = nil) ⇒ Boolean

Verify a BRC-77 signed message.

Parameters:

  • message (String)

    the original message

  • sig (String)

    the binary signature (from sign)

  • recipient (PrivateKey, nil) (defaults to: nil)

    the recipient’s private key (nil for anyone-can-verify)

Returns:

  • (Boolean)

    true if the signature is valid

Raises:

  • (ArgumentError)

    if the version is wrong, recipient is required but missing, or recipient doesn’t match



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/bsv/primitives/signed_message.rb', line 59

def verify(message, sig, recipient = nil)
  sig = sig.b
  raise ArgumentError, "signed message too short: #{sig.bytesize} bytes" if sig.bytesize < 38

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

  sender_pub = PublicKey.from_bytes(sig.byteslice(4, 33))
  verifier_first = sig.getbyte(37)

  if verifier_first.zero?
    # Anyone-can-verify mode
    recipient = PrivateKey.new(OpenSSL::BN.new(1))
    key_id_offset = 38
  else
    # Specific recipient
    verifier_pub_bytes = sig.byteslice(37, 33)
    verifier_pub_hex = verifier_pub_bytes.unpack1('H*')

    if recipient.nil?
      raise ArgumentError,
            "this signature can only be verified with knowledge of a specific private key. The associated public key is: #{verifier_pub_hex}"
    end

    recipient_pub_hex = recipient.public_key.compressed.unpack1('H*')
    if verifier_pub_hex != recipient_pub_hex
      raise ArgumentError,
            "the recipient public key is #{recipient_pub_hex} but the signature requires the recipient to have public key #{verifier_pub_hex}"
    end

    key_id_offset = 70
  end

  key_id = sig.byteslice(key_id_offset, 32)
  der_bytes = sig.byteslice(key_id_offset + 32, sig.bytesize - key_id_offset - 32)

  invoice = "2-message signing-#{[key_id].pack('m0')}"
  signing_pub = sender_pub.derive_child(recipient, invoice)

  signature = Signature.from_der(der_bytes)
  hash = Digest.sha256(message.b)
  ECDSA.verify(hash, signature, signing_pub.point)
end