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.
Constant Summary collapse
- MAGIC_PREFIX =
The standard Bitcoin message signing prefix.
"Bitcoin Signed Message:\n".b.freeze
Class Method Summary collapse
-
.magic_hash(message) ⇒ String
Compute the double-SHA-256 hash of a Bitcoin-prefixed message.
-
.sign(message, private_key) ⇒ String
Sign a message with a private key.
-
.verify(message, signature, address) ⇒ Boolean
Verify a signed message against a Bitcoin address.
Class Method Details
.magic_hash(message) ⇒ String
Compute the double-SHA-256 hash of a Bitcoin-prefixed message.
81 82 83 84 85 86 87 |
# File 'lib/bsv/primitives/bsm.rb', line 81 def magic_hash() = .encode('UTF-8') if .encoding != Encoding::UTF_8 msg_bytes = .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.
30 31 32 33 34 35 36 37 38 |
# File 'lib/bsv/primitives/bsm.rb', line 30 def sign(, private_key) hash = magic_hash() 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.
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(, 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() 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 |