Skip to content

Commit 94ee592

Browse files
authored
Merge pull request #435 from sgbett/feat/426-peer-protocol-completion
feat(auth): peer protocol completion — certificateRequest/certificateResponse and high-level session API
2 parents 7bad4ae + d394cde commit 94ee592

File tree

12 files changed

+3187
-102
lines changed

12 files changed

+3187
-102
lines changed

.claude/plans/20260414-peer-protocol-completion.md

Lines changed: 405 additions & 0 deletions
Large diffs are not rendered by default.

.rubocop.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Metrics/BlockLength:
1818
Exclude:
1919
- 'gem/bsv-wallet/lib/bsv/wallet_interface/wire/**/*'
2020
- 'gem/bsv-wallet-postgres/lib/bsv/wallet_postgres/migrations/**/*'
21+
- 'gem/bsv-sdk/lib/bsv/auth/**/*'
2122
- 'gem/*/spec/**/*'
2223

2324
Naming/FileName:
@@ -279,3 +280,9 @@ RSpec/BeforeAfterAll:
279280
RSpec/InstanceVariable:
280281
Exclude:
281282
- 'gem/bsv-sdk/spec/integration/**/*'
283+
284+
# Callback registration tests need empty blocks to obtain a callback ID without
285+
# triggering an actual callback action.
286+
Lint/EmptyBlock:
287+
Exclude:
288+
- 'gem/*/spec/**/*'

gem/bsv-sdk/lib/bsv/auth.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module Auth
1111
autoload :Certificate, 'bsv/auth/certificate'
1212
autoload :VerifiableCertificate, 'bsv/auth/verifiable_certificate'
1313
autoload :MasterCertificate, 'bsv/auth/master_certificate'
14+
autoload :ValidateCertificates, 'bsv/auth/validate_certificates'
15+
autoload :GetVerifiableCertificates, 'bsv/auth/get_verifiable_certificates'
1416

1517
# Protocol version
1618
AUTH_VERSION = '0.1'
@@ -21,5 +23,29 @@ module Auth
2123
MSG_CERT_REQUEST = 'certificateRequest'
2224
MSG_CERT_RESPONSE = 'certificateResponse'
2325
MSG_GENERAL = 'general'
26+
27+
# Validates certificates attached to an incoming authenticated message.
28+
#
29+
# Delegates to {ValidateCertificates#validate_certificates}.
30+
#
31+
# @param wallet [#verify_signature, #decrypt] the verifier's wallet
32+
# @param message [Hash] incoming authenticated message
33+
# @param requested_certificates [Hash, nil] optional certifier/type filter
34+
# @raise [AuthError] on any validation failure
35+
def self.validate_certificates(wallet, message, requested_certificates = nil)
36+
ValidateCertificates.validate_certificates(wallet, message, requested_certificates)
37+
end
38+
39+
# Retrieves verifiable certificates from a wallet for presentation to a verifier.
40+
#
41+
# Delegates to {GetVerifiableCertificates#get_verifiable_certificates}.
42+
#
43+
# @param wallet [#list_certificates, #prove_certificate] the subject's wallet
44+
# @param requested_certificates [Hash] certifiers and type-to-fields mapping
45+
# @param verifier_identity_key [String] verifier's compressed public key hex
46+
# @return [Array<VerifiableCertificate>]
47+
def self.get_verifiable_certificates(wallet, requested_certificates, verifier_identity_key)
48+
GetVerifiableCertificates.get_verifiable_certificates(wallet, requested_certificates, verifier_identity_key)
49+
end
2450
end
2551
end
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
module BSV
4+
module Auth
5+
# Utility module for retrieving verifiable certificates from a wallet.
6+
#
7+
# Used during the certificate exchange phase of the BSV Auth peer protocol.
8+
# Lists certificates matching the requested certifiers and types, then calls
9+
# +prove_certificate+ for each to obtain a verifier-specific keyring for
10+
# selective field revelation.
11+
#
12+
# NOTE: Issue #424 documents a known bug in +WalletClient#prove_certificate+ — it
13+
# uses the wrong protocol ID (+certificate field revelation+ vs +certificate field
14+
# encryption+) and an incorrect key ID format. Until that bug is fixed, the keyring
15+
# produced here will be cryptographically incompatible with the TS/Go SDKs.
16+
module GetVerifiableCertificates
17+
module_function
18+
19+
# Retrieve verifiable certificates from a wallet for presentation to a verifier.
20+
#
21+
# @param wallet [#list_certificates, #prove_certificate] the subject's wallet.
22+
# Duck-typed — if the wallet does not respond to both methods, returns +[]+.
23+
# @param requested_certificates [Hash] with keys:
24+
# - +:certifiers+ [Array<String>] list of certifier public key hexes
25+
# - +:types+ [Hash] type (Base64 string) → array of field names to reveal
26+
# @param verifier_identity_key [String] the verifier's compressed public key hex
27+
# @return [Array<VerifiableCertificate>] list of verifiable certificates ready for
28+
# presentation, or +[]+ on any failure
29+
def get_verifiable_certificates(wallet, requested_certificates, verifier_identity_key)
30+
return [] unless wallet.respond_to?(:list_certificates) && wallet.respond_to?(:prove_certificate)
31+
32+
certifiers = requested_certificates[:certifiers] || requested_certificates['certifiers'] || []
33+
types_map = requested_certificates[:types] || requested_certificates['types'] || {}
34+
35+
list_result = wallet.list_certificates(
36+
certifiers: certifiers,
37+
types: types_map.keys
38+
)
39+
40+
certificates = list_result[:certificates] || list_result['certificates'] || []
41+
return [] if certificates.empty?
42+
43+
certificates.map do |cert|
44+
cert_type = cert[:type] || cert['type']
45+
fields_to_reveal = types_map[cert_type] || types_map[cert_type.to_s] || types_map[cert_type.to_sym] || []
46+
47+
prove_result = wallet.prove_certificate(
48+
certificate: cert,
49+
fields_to_reveal: fields_to_reveal,
50+
verifier: verifier_identity_key
51+
)
52+
53+
keyring = prove_result[:keyring_for_verifier] ||
54+
prove_result['keyring_for_verifier'] ||
55+
prove_result[:keyringForVerifier] ||
56+
prove_result['keyringForVerifier'] ||
57+
{}
58+
59+
VerifiableCertificate.new(
60+
type: cert_type,
61+
serial_number: cert[:serial_number] || cert['serial_number'] ||
62+
cert[:serialNumber] || cert['serialNumber'],
63+
subject: cert[:subject] || cert['subject'],
64+
certifier: cert[:certifier] || cert['certifier'],
65+
revocation_outpoint: cert[:revocation_outpoint] || cert['revocation_outpoint'] ||
66+
cert[:revocationOutpoint] || cert['revocationOutpoint'],
67+
fields: cert[:fields] || cert['fields'] || {},
68+
keyring: keyring,
69+
signature: cert[:signature] || cert['signature']
70+
)
71+
end
72+
rescue StandardError
73+
# Auto-fetch is best-effort: wallet may raise UnsupportedActionError,
74+
# key derivation errors, or other failures. The peer protocol handles
75+
# "no certificates" gracefully — the requesting peer enforces its own
76+
# certificate requirements independently.
77+
[]
78+
end
79+
end
80+
end
81+
end

0 commit comments

Comments
 (0)