Class: X402::BSV::BRC105Gateway
- Inherits:
-
Object
- Object
- X402::BSV::BRC105Gateway
- Defined in:
- lib/x402/bsv/brc105_gateway.rb
Overview
BRC-105 gateway for BSV settlement-gated HTTP.
Unlike PayGateway/ProofGateway, this does NOT inherit from Gateway. BRC-105 uses a fundamentally different pattern: no partial tx template, no OP_RETURN binding, no payTo HMAC. Key derivation (BRC-42/43) via KeyDeriver replaces the template-based approach.
Implements the three-method gateway interface required by Middleware:
challenge_headers(rack_request, route) → Hash
proof_header_names → Array
Constant Summary collapse
- PROTOCOL_ID =
[2, "3241645161d8"].freeze
- PROOF_HEADER =
"x-bsv-payment"- PROTOCOL =
"wallet payment"- COMPRESSED_PUBKEY_HEX =
/\A0[23][0-9a-f]{64}\z/- MAX_DERIVATION_BYTES =
64- PRINTABLE_ASCII =
/\A[\x20-\x7E]+\z/
Instance Method Summary collapse
-
#challenge_headers(rack_request, route) ⇒ Hash
Issue a 402 challenge with BRC-105 headers.
-
#initialize(key_deriver:, prefix_store:, wallet:, arc_client: nil) ⇒ BRC105Gateway
constructor
A new instance of BRC105Gateway.
-
#proof_header_names ⇒ Array<String>
Header names that carry the proof/payment from the client.
-
#settle!(_header_name, proof_payload, rack_request, route) ⇒ SettlementResult
Verify and internalise a BRC-105 payment.
Constructor Details
#initialize(key_deriver:, prefix_store:, wallet:, arc_client: nil) ⇒ BRC105Gateway
Returns a new instance of BRC105Gateway.
45 46 47 48 49 50 |
# File 'lib/x402/bsv/brc105_gateway.rb', line 45 def initialize(key_deriver:, prefix_store:, wallet:, arc_client: nil) @key_deriver = key_deriver @prefix_store = prefix_store @wallet = wallet @arc_client = arc_client end |
Instance Method Details
#challenge_headers(rack_request, route) ⇒ Hash
Issue a 402 challenge with BRC-105 headers.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/x402/bsv/brc105_gateway.rb', line 57 def challenge_headers(rack_request, route) prefix = SecureRandom.hex(16) begin @prefix_store.store!(prefix) rescue PrefixStore::StoreFullError raise VerificationError.new("server at capacity — try again later", status: 503) end headers = { "x-bsv-payment-version" => "1.0", "x-bsv-payment-satoshis-required" => route.resolve_amount_sats.to_s, "x-bsv-payment-derivation-prefix" => prefix } # The 402 challenge is issued before the client authenticates (no # x-bsv-auth-identity-key yet). Include the server's identity key so # the client knows who to derive the payment address for. When BRC-103 # mutual auth is already established, the client already has this key. headers["x-bsv-payment-identity-key"] = @key_deriver.identity_key unless validated_brc103_key(rack_request) headers end |
#proof_header_names ⇒ Array<String>
Header names that carry the proof/payment from the client.
83 84 85 |
# File 'lib/x402/bsv/brc105_gateway.rb', line 83 def proof_header_names [PROOF_HEADER] end |
#settle!(_header_name, proof_payload, rack_request, route) ⇒ SettlementResult
Verify and internalise a BRC-105 payment.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/x402/bsv/brc105_gateway.rb', line 94 def settle!(_header_name, proof_payload, rack_request, route) # §7.1: fail fast if unauthenticated — before parsing untrusted payload counterparty = resolve_counterparty(rack_request) required_sats = route.resolve_amount_sats payment = parse_payment(proof_payload) prefix = payment["derivationPrefix"] suffix = payment["derivationSuffix"] validate_prefix_and_suffix!(prefix, suffix) subject_tx = parse_beef_transaction(payment["transaction"]) log_derivation_inputs(prefix, suffix, counterparty) expected_script = derive_payment_script(prefix, suffix, rack_request) log_expected_script(expected_script) log_tx_outputs(subject_tx, required_sats, expected_script) paid_sats, output_index = verify_payment_output!(subject_tx, required_sats, expected_script) # Broadcast BEFORE consume_prefix! so a legitimate retry after a # re-broadcast can still use the prefix. Consuming first would # make the subsequent attempt fail the replay check regardless # of whether the client fixed their broadcast. broadcast!(subject_tx, route) consume_prefix!(prefix) internalize_payment!( transaction_b64: payment["transaction"], output_index: output_index, derivation_prefix: prefix, derivation_suffix: suffix, sender_identity_key: counterparty ) log_settlement_success(subject_tx, paid_sats, required_sats) build_settlement_result(subject_tx, paid_sats) end |