Module: BSV::Wallet::CertificateSignature

Defined in:
lib/bsv/wallet_interface/certificate_signature.rb

Overview

BRC-52 identity certificate signature verification.

Certificates carry a signature from the certifier over a canonical binary serialisation of their fields (excluding the signature itself). This module builds that canonical serialisation and delegates verification to a ProtoWallet-compatible verifier.

Every field is included in the preimage in this order:

  • type (base64 → 32 bytes)

  • serial_number (base64 → 32 bytes)

  • subject (hex → 33-byte compressed pubkey)

  • certifier (hex → 33-byte compressed pubkey)

  • revocation_outpoint: txid hex (32 bytes) + output index VarInt

  • fields: VarInt count, then for each field (sorted lexicographically by name): VarInt name length + UTF-8 name bytes + VarInt value length + UTF-8 value bytes

Signing uses BRC-42 key derivation with:

  • protocol ID: [2, ‘certificate signature’]

  • key ID: “#{type} #{serial_number}”

  • counterparty on sign: ‘anyone’ (default of ProtoWallet#create_signature in TS — Ruby consumers should pass it explicitly since Ruby defaults to ‘self’)

  • counterparty on verify: the certifier’s public key hex

See Also:

Defined Under Namespace

Classes: InvalidError

Constant Summary collapse

PROTOCOL_ID =
[2, 'certificate signature'].freeze

Class Method Summary collapse

Class Method Details

.serialise_preimage(cert) ⇒ String

Build the BRC-52 canonical preimage for signing or verifying.

Parameters:

  • cert (Hash)

    certificate fields (see verify!)

Returns:



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/bsv/wallet_interface/certificate_signature.rb', line 93

def serialise_preimage(cert)
  buf = String.new(encoding: Encoding::ASCII_8BIT)
  buf << decode_base64_exact(cert[:type], 32, 'type')
  buf << decode_base64_exact(cert[:serial_number], 32, 'serial_number')
  buf << decode_hex_exact(cert[:subject], 33, 'subject')
  buf << decode_hex_exact(cert[:certifier], 33, 'certifier')

  buf << encode_revocation_outpoint(cert[:revocation_outpoint])
  buf << encode_fields(cert[:fields])
  buf
end

.verify!(cert, verifier: ProtoWallet.new('anyone')) ⇒ true

Verify a certificate’s certifier signature.

Raises InvalidError if the signature is missing, malformed, or does not match the expected certifier.

Parameters:

  • cert (Hash)

    certificate fields. Required keys: :type, :serial_number, :subject, :certifier, :revocation_outpoint, :fields, :signature

  • verifier (#verify_signature) (defaults to: ProtoWallet.new('anyone'))

    optional verifier; defaults to a fresh ProtoWallet.new(‘anyone’)

Returns:

  • (true)

    when the signature verifies

Raises:



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
# File 'lib/bsv/wallet_interface/certificate_signature.rb', line 59

def verify!(cert, verifier: ProtoWallet.new('anyone'))
  signature_hex = cert[:signature]
  raise InvalidError, 'signature is missing' if signature_hex.nil? || signature_hex.empty?

  preimage = serialise_preimage(cert)
  sig_bytes = hex_to_bytes(signature_hex)

  verifier.verify_signature({
                              data: preimage.unpack('C*'),
                              signature: sig_bytes,
                              protocol_id: PROTOCOL_ID,
                              key_id: "#{cert[:type]} #{cert[:serial_number]}",
                              counterparty: cert[:certifier]
                            })

  true
rescue InvalidSignatureError => e
  raise if e.is_a?(InvalidError)

  raise InvalidError, e.message
rescue ArgumentError, EncodingError => e
  # EncodingError covers Encoding::InvalidByteSequenceError and
  # Encoding::UndefinedConversionError, which `encode_fields`
  # raises for non-UTF-8 field names or values. Callers of
  # `acquire_certificate` expect `InvalidError` on bad cert input
  # — leaking EncodingError would break that contract.
  raise InvalidError, e.message
end