Class: X402::BSV::BRC105Gateway

Inherits:
Object
  • Object
show all
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 settle!(header_name, proof_payload, rack_request, route) → SettlementResult

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

Constructor Details

#initialize(key_deriver:, prefix_store:, wallet:, arc_client: nil) ⇒ BRC105Gateway

Returns a new instance of BRC105Gateway.

Parameters:

  • key_deriver (BSV::Wallet::KeyDeriver)

    provides identity key + BRC-42 derivation

  • prefix_store (#store!, #valid?, #consume!)

    replay protection for derivation prefixes

  • wallet (#internalize_action)

    BRC-100 wallet for payment internalisation

  • arc_client (BSV::Network::ARC, nil) (defaults to: nil)

    optional ARC client for broadcasting BEEF



33
34
35
36
37
38
# File 'lib/x402/bsv/brc105_gateway.rb', line 33

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.

Parameters:

Returns:

  • (Hash)

    challenge headers (x-bsv-* namespace)



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/x402/bsv/brc105_gateway.rb', line 45

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_namesArray<String>

Header names that carry the proof/payment from the client.

Returns:

  • (Array<String>)


71
72
73
# File 'lib/x402/bsv/brc105_gateway.rb', line 71

def proof_header_names
  [PROOF_HEADER]
end

#settle!(_header_name, proof_payload, rack_request, route) ⇒ SettlementResult

Verify and internalise a BRC-105 payment.

Parameters:

  • _header_name (String)

    which proof header matched

  • proof_payload (String)

    raw header value

  • rack_request (Rack::Request)
  • route (X402::Configuration::Route)

Returns:



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/x402/bsv/brc105_gateway.rb', line 82

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)
  consume_prefix!(prefix)
  broadcast_to_arc!(payment["transaction"])
  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