Module: BSV::Primitives::BSM

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

Overview

Bitcoin Signed Messages (BSM).

Signs and verifies messages using the standard Bitcoin message signing protocol. Messages are prefixed with “Bitcoin Signed Message:n”, length-prefixed, and double-SHA-256 hashed before signing with recoverable ECDSA. Signatures are 65-byte compact format, base64-encoded.

Examples:

Sign and verify a message

key = BSV::Primitives::PrivateKey.generate
sig = BSV::Primitives::BSM.sign('hello', key)
BSV::Primitives::BSM.verify('hello', sig, key.public_key.address) #=> true

Constant Summary collapse

MAGIC_PREFIX =

The standard Bitcoin message signing prefix.

"Bitcoin Signed Message:\n".b.freeze

Class Method Summary collapse

Class Method Details

.magic_hash(message) ⇒ String

Compute the double-SHA-256 hash of a Bitcoin-prefixed message.

Parameters:

  • message (String)

    the message to hash

Returns:

  • (String)

    32-byte double-SHA-256 digest



81
82
83
84
85
86
87
# File 'lib/bsv/primitives/bsm.rb', line 81

def magic_hash(message)
  message = message.encode('UTF-8') if message.encoding != Encoding::UTF_8
  msg_bytes = message.b
  buf = encode_varint(MAGIC_PREFIX.bytesize) + MAGIC_PREFIX +
        encode_varint(msg_bytes.bytesize) + msg_bytes
  Digest.sha256d(buf)
end

.sign(message, private_key) ⇒ String

Sign a message with a private key.

Produces a 65-byte compact recoverable signature encoded as base64. The flag byte (31-34) indicates compressed P2PKH recovery per BIP-137.

Parameters:

  • message (String)

    the message to sign

  • private_key (PrivateKey)

    the signing key

Returns:

  • (String)

    base64-encoded compact signature



30
31
32
33
34
35
36
37
38
# File 'lib/bsv/primitives/bsm.rb', line 30

def sign(message, private_key)
  hash = magic_hash(message)
  sig, recovery_id = ECDSA.sign_recoverable(hash, private_key.bn)

  # Flag byte: 31-34 = compressed P2PKH (BIP-137)
  flag = 31 + recovery_id
  compact = [flag].pack('C') + bn_to_bytes(sig.r) + bn_to_bytes(sig.s)
  [compact].pack('m0')
end

.verify(message, signature, address) ⇒ Boolean

Verify a signed message against a Bitcoin address.

Recovers the public key from the compact signature and checks whether the derived address matches the expected address.

Parameters:

  • message (String)

    the original message

  • signature (String)

    base64-encoded compact signature

  • address (String)

    the expected Bitcoin address

Returns:

  • (Boolean)

    true if the signature is valid for the given address

Raises:

  • (ArgumentError)

    if the signature encoding or flag byte is invalid



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
# File 'lib/bsv/primitives/bsm.rb', line 50

def verify(message, signature, address)
  compact = decode_compact(signature)
  flag = compact.getbyte(0)
  validate_flag!(flag)

  recovery_id = (flag - 27) & 3
  compressed = flag >= 31

  r_bn = OpenSSL::BN.new(compact[1, 32], 2)
  s_bn = OpenSSL::BN.new(compact[33, 32], 2)
  sig = Signature.new(r_bn, s_bn)

  hash = magic_hash(message)
  pub = ECDSA.recover_public_key(hash, sig, recovery_id)

  derived = if compressed
              pub.address
            else
              h160 = Digest.hash160(pub.uncompressed)
              Base58.check_encode(PublicKey::MAINNET_PUBKEY_HASH + h160)
            end

  derived == address
rescue OpenSSL::PKey::EC::Point::Error
  false
end