Class: BSV::Auth::VerifiableCertificate

Inherits:
Certificate show all
Defined in:
lib/bsv/auth/verifiable_certificate.rb

Overview

A Certificate subclass for selective field revelation on the verifier side.

VerifiableCertificate holds a verifier-specific keyring — a map from field names to Base64-encoded symmetric keys encrypted for the verifier. Calling #decrypt_fields decrypts each entry in the keyring using the verifier’s wallet and then uses the recovered key to decrypt the corresponding field value from the base certificate.

Protocol details

Each keyring entry is decrypted via BRC-42 key derivation:

  • Protocol: [2, ‘certificate field encryption’]

  • Key ID: “#{serial_number} #{field_name}”

  • Counterparty: the certificate subject public key

Wallet parameters are duck-typed — any object responding to :decrypt is accepted. No direct dependency on BSV::Wallet::ProtoWallet is introduced here.

Constant Summary

Constants inherited from Certificate

Certificate::CERT_FIELD_ENC_PROTOCOL, Certificate::CERT_SIG_PROTOCOL

Instance Attribute Summary collapse

Attributes inherited from Certificate

#certifier, #fields, #revocation_outpoint, #serial_number, #signature, #subject, #type

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Certificate

certificate_field_encryption_details, from_binary, #sign, #to_binary, #verify

Constructor Details

#initialize(type:, serial_number:, subject:, certifier:, revocation_outpoint:, fields:, keyring:, signature: nil, decrypted_fields: nil) ⇒ VerifiableCertificate

Returns a new instance of VerifiableCertificate.

Parameters:

  • type (String)

    Base64 string (32 bytes decoded)

  • serial_number (String)

    Base64 string (32 bytes decoded)

  • subject (String)

    compressed public key hex

  • certifier (String)

    compressed public key hex

  • revocation_outpoint (String)

    “<txid_hex>.<output_index>”

  • fields (Hash)

    field name strings to encrypted value strings (Base64)

  • keyring (Hash)

    field name strings to Base64-encoded verifier-specific encrypted keys

  • signature (String, nil) (defaults to: nil)

    DER-encoded signature hex, or nil

  • decrypted_fields (Hash, nil) (defaults to: nil)

    pre-populated decrypted fields, or nil



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/bsv/auth/verifiable_certificate.rb', line 45

def initialize(type:, serial_number:, subject:, certifier:, revocation_outpoint:,
               fields:, keyring:, signature: nil, decrypted_fields: nil)
  super(
    type: type,
    serial_number: serial_number,
    subject: subject,
    certifier: certifier,
    revocation_outpoint: revocation_outpoint,
    fields: fields,
    signature: signature
  )
  @keyring          = keyring
  @decrypted_fields = decrypted_fields
end

Instance Attribute Details

#decrypted_fieldsHash? (readonly)

Returns field name → decrypted plaintext string, or nil before decryption.

Returns:

  • (Hash, nil)

    field name → decrypted plaintext string, or nil before decryption



34
35
36
# File 'lib/bsv/auth/verifiable_certificate.rb', line 34

def decrypted_fields
  @decrypted_fields
end

#keyringHash (readonly)

Returns field name → Base64-encoded verifier-specific encrypted key.

Returns:

  • (Hash)

    field name → Base64-encoded verifier-specific encrypted key



31
32
33
# File 'lib/bsv/auth/verifiable_certificate.rb', line 31

def keyring
  @keyring
end

Class Method Details

.from_certificate(certificate, keyring) ⇒ VerifiableCertificate

Construct a VerifiableCertificate from a base Certificate and a keyring.

Parameters:

  • certificate (Certificate)

    any Certificate instance (or duck-typed object responding to type, serial_number, subject, certifier, revocation_outpoint, fields, signature)

  • keyring (Hash)

    field name → Base64-encoded verifier-specific encrypted key

Returns:



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/bsv/auth/verifiable_certificate.rb', line 67

def self.from_certificate(certificate, keyring)
  new(
    type: certificate.type,
    serial_number: certificate.serial_number,
    subject: certificate.subject,
    certifier: certificate.certifier,
    revocation_outpoint: certificate.revocation_outpoint,
    fields: certificate.fields,
    keyring: keyring,
    signature: certificate.signature
  )
end

.from_hash(hash) ⇒ VerifiableCertificate

Construct a VerifiableCertificate from a plain Hash.

Accepts both snake_case and camelCase key variants.

Parameters:

  • hash (Hash)

    certificate data including keyring and optional decrypted_fields

Returns:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/bsv/auth/verifiable_certificate.rb', line 86

def self.from_hash(hash)
  h = normalise_hash_keys(hash)
  new(
    type: h['type'],
    serial_number: h['serial_number'],
    subject: h['subject'],
    certifier: h['certifier'],
    revocation_outpoint: h['revocation_outpoint'],
    fields: h['fields'] || {},
    keyring: h['keyring'] || {},
    signature: h['signature'],
    decrypted_fields: h['decrypted_fields']
  )
end

Instance Method Details

#decrypt_fields(verifier_wallet, privileged: false, privileged_reason: nil) ⇒ Hash

Decrypt selectively revealed certificate fields using the verifier’s wallet.

Algorithm:

  1. Raises if keyring is nil or empty.

  2. For each field in keyring:

    1. Decrypts the encrypted revelation key via verifier_wallet.decrypt, using protocol [2, ‘certificate field encryption’] and key_id “#{serial_number} #{field_name}”, counterparty = subject.

    2. Uses the recovered bytes as a Primitives::SymmetricKey.

    3. Decrypts the corresponding encrypted field value.

    4. Stores the UTF-8 plaintext.

  3. Caches and returns the decrypted fields hash.

Parameters:

  • verifier_wallet (#decrypt)

    wallet belonging to the verifier

  • privileged (Boolean) (defaults to: false)

    whether this is a privileged operation

  • privileged_reason (String, nil) (defaults to: nil)

    reason for privileged access

Returns:

  • (Hash)

    field name → decrypted plaintext string

Raises:

  • (ArgumentError)

    if keyring is nil or empty

  • (RuntimeError)

    if decryption fails for any field



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/bsv/auth/verifiable_certificate.rb', line 120

def decrypt_fields(verifier_wallet, privileged: false, privileged_reason: nil)
  raise ArgumentError, 'A keyring is required to decrypt certificate fields for the verifier.' if @keyring.nil? || @keyring.empty?

  begin
    result = {}
    @keyring.each do |field_name, encrypted_key_b64|
      dec_args = {
        ciphertext: Base64.strict_decode64(encrypted_key_b64).bytes,
        counterparty: @subject,
        privileged: privileged,
        privileged_reason: privileged_reason
      }.merge(Certificate.certificate_field_encryption_details(field_name, @serial_number))

      field_revelation_key = verifier_wallet.decrypt(dec_args)[:plaintext]

      sym_key = BSV::Primitives::SymmetricKey.new(field_revelation_key.pack('C*'))
      decrypted_bytes = sym_key.decrypt(Base64.strict_decode64(@fields[field_name]))
      result[field_name] = decrypted_bytes.force_encoding('UTF-8')
    end

    @decrypted_fields = result
    result
  rescue ArgumentError
    raise
  rescue StandardError
    raise 'Failed to decrypt selectively revealed certificate fields using keyring.'
  end
end

#to_hHash

Return the certificate as a plain Hash with snake_case keys, including keyring and decrypted_fields (if present).

Returns:

  • (Hash)


153
154
155
156
157
158
# File 'lib/bsv/auth/verifiable_certificate.rb', line 153

def to_h
  h = super
  h['keyring'] = @keyring.dup
  h['decrypted_fields'] = @decrypted_fields.dup if @decrypted_fields
  h
end