Module: BSV::Auth::Nonce
- Defined in:
- lib/bsv/auth/nonce.rb
Overview
Nonce creation and verification for BRC-31 mutual authentication.
A nonce is a 48-byte value: 16 random bytes concatenated with a 32-byte HMAC-SHA256 over those 16 bytes, base64-encoded. The HMAC is computed with the wallet using protocol [2, ‘server hmac’] and the raw bytes as the key ID (decoded to a string). This makes nonces self-authenticating —only the wallet that created a nonce can verify it.
The key ID is derived by decoding the 16 random bytes as UTF-8, replacing any invalid byte sequences with the Unicode replacement character (U+FFFD). This matches the ts-sdk behaviour (TextDecoder in non-fatal replacement mode). Since nonces are always self-verified (counterparty=‘self’), the key ID encoding does not need to be interoperable across SDK implementations.
Constant Summary collapse
- PROTOCOL_ID =
[2, 'server hmac'].freeze
- RANDOM_BYTES =
16
Class Method Summary collapse
-
.create(wallet, counterparty = 'self') ⇒ String
Creates a self-authenticating nonce.
-
.decode_as_utf8(bytes) ⇒ String
Decodes a binary string as UTF-8, replacing invalid byte sequences with the Unicode replacement character (U+FFFD).
-
.verify(nonce, wallet, counterparty = 'self') ⇒ Boolean
Verifies that a nonce was created by the given wallet.
Class Method Details
.create(wallet, counterparty = 'self') ⇒ String
Creates a self-authenticating nonce.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/bsv/auth/nonce.rb', line 33 def create(wallet, counterparty = 'self') first_half = SecureRandom.random_bytes(RANDOM_BYTES) key_id = decode_as_utf8(first_half) result = wallet.create_hmac({ data: first_half.bytes, protocol_id: PROTOCOL_ID, key_id: key_id, counterparty: counterparty }) nonce_bytes = first_half + result[:hmac].pack('C*') ::Base64.strict_encode64(nonce_bytes) end |
.decode_as_utf8(bytes) ⇒ String
Decodes a binary string as UTF-8, replacing invalid byte sequences with the Unicode replacement character (U+FFFD). Used to derive the HMAC key ID from the random nonce bytes in a way consistent with the ts-sdk’s use of TextDecoder (non-fatal mode).
83 84 85 |
# File 'lib/bsv/auth/nonce.rb', line 83 def decode_as_utf8(bytes) bytes.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: "\uFFFD") end |
.verify(nonce, wallet, counterparty = 'self') ⇒ Boolean
Verifies that a nonce was created by the given wallet.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/bsv/auth/nonce.rb', line 55 def verify(nonce, wallet, counterparty = 'self') nonce_bytes = ::Base64.strict_decode64(nonce) return false if nonce_bytes.bytesize <= RANDOM_BYTES first_half = nonce_bytes.byteslice(0, RANDOM_BYTES) hmac_bytes = nonce_bytes.byteslice(RANDOM_BYTES, nonce_bytes.bytesize - RANDOM_BYTES) key_id = decode_as_utf8(first_half) wallet.verify_hmac({ data: first_half.bytes, hmac: hmac_bytes.bytes, protocol_id: PROTOCOL_ID, key_id: key_id, counterparty: counterparty }) true rescue BSV::Wallet::InvalidHmacError false end |