bsv-rs 0.3.5

BSV blockchain SDK for Rust - primitives, script, transactions, and more
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
108
109
110
111
112
113
114
115
116
117
118
119
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# Auth Utilities
> Nonce creation/verification and certificate validation for BRC-31 authentication

## Overview

This submodule provides utility functions supporting the BRC-31 (Authrite) authentication protocol. It handles two critical aspects:

1. **Nonce Management**: Cryptographic nonce creation and verification to prevent replay attacks
2. **Certificate Validation**: Validation and retrieval of BRC-52/53 certificates

These utilities are used internally by the `Peer` implementation but are also exported for direct use.

## Files

| File | Purpose | Lines |
|------|---------|-------|
| `mod.rs` | Module root with re-exports | ~16 |
| `nonce.rs` | Cryptographic nonce creation and verification | ~201 |
| `validation.rs` | Certificate validation, encoding checks, and retrieval | ~972 |

## Key Exports

### Nonce Functions

```rust
pub use nonce::{
    create_nonce,
    verify_nonce,
    validate_nonce_format,
    get_nonce_random,
    NONCE_PROTOCOL,
};
```

### Validation Functions

```rust
pub use validation::{
    validate_certificates,
    validate_certificate,
    validate_certificate_encoding,
    validate_requested_certificate_set,
    get_verifiable_certificates,
    certificates_match_request,
};
```

## Nonce Module

### Nonce Format

Nonces are 32 bytes encoded as base64:
- **First 16 bytes**: Cryptographically secure random data
- **Last 16 bytes**: HMAC of the random data (truncated from 32-byte HMAC)

The HMAC is computed using BRC-42 key derivation with:
- **Protocol**: `"server hmac"` (exported as `NONCE_PROTOCOL`)
- **Security Level**: App
- **Key ID**: base64-encoded random portion

### create_nonce

```rust
pub async fn create_nonce<W: WalletInterface>(
    wallet: &W,
    counterparty: Option<&PublicKey>,
    originator: &str,
) -> Result<String>
```

Creates a cryptographic nonce for authentication sessions.

**Parameters:**
- `wallet` - Wallet implementing `WalletInterface` for HMAC computation
- `counterparty` - Optional counterparty public key (None = self-authentication)
- `originator` - Application originator string (e.g., `"myapp.com"`)

**Returns:** Base64-encoded nonce string (44 characters for 32 bytes)

**Example:**
```rust
use bsv_rs::auth::utils::create_nonce;

let nonce = create_nonce(&wallet, Some(&peer_key), "myapp.com").await?;
// Returns something like: "Y3J5cHRvZ3JhcGhpY2FsbHlfc2VjdXJlX25vbmNl..."
```

### verify_nonce

```rust
pub async fn verify_nonce<W: WalletInterface>(
    nonce: &str,
    wallet: &W,
    counterparty: Option<&PublicKey>,
    originator: &str,
) -> Result<bool>
```

Verifies that a nonce was created by the expected party.

**Parameters:**
- `nonce` - Base64-encoded nonce to verify
- `wallet` - Wallet for HMAC verification
- `counterparty` - Optional counterparty who created the nonce
- `originator` - Application originator string

**Returns:** `true` if valid, `false` if HMAC doesn't match

**Errors:** Returns `Error::InvalidNonce` if nonce is too short (< 32 bytes)

**Example:**
```rust
use bsv_rs::auth::utils::verify_nonce;

let is_valid = verify_nonce(&peer_nonce, &wallet, Some(&peer_key), "myapp.com").await?;
if !is_valid {
    return Err(Error::AuthError("Invalid peer nonce".into()));
}
```

### validate_nonce_format

```rust
pub fn validate_nonce_format(nonce: &str) -> Result<()>
```

Quick format check without cryptographic verification. Validates:
- Valid base64 encoding
- At least 32 bytes when decoded

**Example:**
```rust
use bsv_rs::auth::utils::validate_nonce_format;

// Fast check before expensive verification
validate_nonce_format(&incoming_nonce)?;
```

### get_nonce_random

```rust
pub fn get_nonce_random(nonce: &str) -> Result<Vec<u8>>
```

Extracts the 16-byte random portion from a nonce. Useful for using the nonce as a session identifier.

**Example:**
```rust
use bsv_rs::auth::utils::get_nonce_random;

let random_bytes = get_nonce_random(&session_nonce)?;
let session_id = hex::encode(&random_bytes);
```

### NONCE_PROTOCOL

```rust
pub const NONCE_PROTOCOL: &str = "server hmac";
```

The BRC-42 protocol identifier used for nonce HMAC computation.

## Validation Module

### validate_certificates

```rust
pub async fn validate_certificates<W: WalletInterface>(
    verifier_wallet: &W,
    message: &AuthMessage,
    certificates_requested: Option<&RequestedCertificateSet>,
    originator: &str,
) -> Result<()>
```

Validates all certificates in an authentication message.

**Validation Steps:**
1. Certificate subject matches message sender (`identity_key`)
2. Certificate signature is valid (cryptographic verification)
3. Certifiers are in the trusted set (if `certificates_requested` specifies certifiers)
4. Certificate types match requested types (if specified)
5. Required fields are present in keyring
6. Field decryption succeeds with verifier's wallet

**Parameters:**
- `verifier_wallet` - Wallet for field decryption attempts
- `message` - The `AuthMessage` containing certificates
- `certificates_requested` - Optional `RequestedCertificateSet` with requirements
- `originator` - Application originator

**Errors:**
- `Error::AuthError("Required certificates not provided")` - Certificates requested but none in message
- `Error::AuthError("Certificate subject does not match message sender")` - Subject mismatch
- `Error::AuthError("Certificate signature invalid")` - Signature verification failed
- `Error::AuthError("Certificate from untrusted certifier: {hex}")` - Certifier not in trusted list
- `Error::AuthError("Certificate type not in requested set: {type}")` - Type not requested
- `Error::AuthError("Required field '{name}' not revealed in certificate")` - Missing keyring entry

**Example:**
```rust
use bsv_rs::auth::utils::validate_certificates;

validate_certificates(
    &my_wallet,
    &incoming_message,
    Some(&required_certs),
    "myapp.com"
).await?;
```

### validate_certificate

```rust
pub async fn validate_certificate<W: WalletInterface>(
    verifier_wallet: &W,
    cert: &VerifiableCertificate,
    sender_key: &PublicKey,
    certificates_requested: Option<&RequestedCertificateSet>,
    originator: &str,
) -> Result<()>
```

Validates a single certificate. Called by `validate_certificates` for each certificate, but can be used directly for granular control.

**Example:**
```rust
use bsv_rs::auth::utils::validate_certificate;

for cert in &message.certificates.unwrap_or_default() {
    validate_certificate(
        &wallet,
        cert,
        &message.identity_key,
        Some(&requirements),
        "myapp.com"
    ).await?;
}
```

### get_verifiable_certificates

```rust
pub async fn get_verifiable_certificates<W: WalletInterface>(
    wallet: &W,
    requested: &RequestedCertificateSet,
    verifier_identity_key: &PublicKey,
    originator: &str,
) -> Result<Vec<VerifiableCertificate>>
```

Retrieves certificates from wallet matching a request and creates verifiable versions with keyrings for the specified verifier.

**Process:**
1. Queries wallet via `list_certificates()` with certifiers and types from request
2. For each matching certificate, calls `prove_certificate()` to create keyring
3. Converts wallet certificate format to `VerifiableCertificate` with keyring
4. Skips malformed certificates (invalid type/serial length, invalid keys)

**Parameters:**
- `wallet` - Wallet containing certificates to retrieve
- `requested` - `RequestedCertificateSet` specifying which certificates and fields
- `verifier_identity_key` - Public key of the party who will verify (for keyring creation)
- `originator` - Application originator

**Example:**
```rust
use bsv_rs::auth::utils::get_verifiable_certificates;
use bsv_rs::auth::RequestedCertificateSet;

let mut requested = RequestedCertificateSet::new();
requested.add_certifier(certifier_hex);
requested.add_type(cert_type_base64, vec!["name".into(), "email".into()]);

let certs = get_verifiable_certificates(
    &wallet,
    &requested,
    &verifier_key,
    "myapp.com"
).await?;
```

### certificates_match_request

```rust
pub fn certificates_match_request(
    certs: &[VerifiableCertificate],
    requested: &RequestedCertificateSet,
) -> bool
```

Quick check if certificates satisfy a request without cryptographic verification.

**Checks:**
- Each requested type has a matching certificate
- Matching certificates have trusted certifiers (if certifiers specified)
- Required fields have keyring entries

**Use Case:** Fast pre-check before expensive cryptographic validation.

**Example:**
```rust
use bsv_rs::auth::utils::certificates_match_request;

if !certificates_match_request(&peer_certs, &my_requirements) {
    return Err(Error::AuthError("Certificates don't meet requirements".into()));
}
// Now do full cryptographic validation
validate_certificates(&wallet, &message, Some(&my_requirements), "myapp.com").await?;
```

### validate_certificate_encoding

```rust
pub fn validate_certificate_encoding(
    cert: &Certificate,
) -> Result<()>
```

Validates the structural encoding of a certificate without cryptographic verification. Analogous to the TypeScript SDK's `validationHelpers.ts`.

**Checks:**
- Certificate type is exactly 32 bytes (always valid by construction)
- Serial number is exactly 32 bytes (always valid by construction)
- Subject and certifier are valid compressed public keys (33 bytes, round-trip parse)
- All field names are non-empty and at most 50 bytes (UTF-8)
- If signature is present, it is non-empty and valid DER encoding
- If revocation outpoint is present, it is not the null sentinel (all-zero txid, vout 0)

**Errors:** Returns `Error::CertificateValidationError(String)` describing the first failure found.

**Example:**
```rust
use bsv_rs::auth::utils::validate_certificate_encoding;

let cert = Certificate::new([1u8; 32], [2u8; 32], subject, certifier);
validate_certificate_encoding(&cert)?;
```

### validate_requested_certificate_set

```rust
pub fn validate_requested_certificate_set(
    requested: &RequestedCertificateSet,
) -> Result<()>
```

Validates that a `RequestedCertificateSet` is well-formed. Analogous to the TypeScript SDK's validation of certificate-related arguments.

**Checks:**
- At least one type must be specified (empty types = error)
- All certifier entries are valid hex-encoded 33-byte compressed public keys (66 hex chars)
- All type IDs are valid base64 strings that decode to exactly 32 bytes
- All field names within each type are non-empty and at most 50 bytes (UTF-8)
- No certifiers is valid (any certifier accepted); no fields for a type is valid (just checks type exists)

**Errors:** Returns `Error::CertificateValidationError(String)` describing the first failure found.

**Example:**
```rust
use bsv_rs::auth::{RequestedCertificateSet, utils::validate_requested_certificate_set};

let mut req = RequestedCertificateSet::new();
req.add_certifier("02abc...".to_string());
req.add_type("base64_type_id".to_string(), vec!["name".into(), "email".into()]);

validate_requested_certificate_set(&req)?;
```

## Usage Patterns

### Session Nonce Flow

```rust
use bsv_rs::auth::utils::{create_nonce, verify_nonce};

// Initiator creates nonce
let my_nonce = create_nonce(&wallet, None, "myapp.com").await?;

// Send nonce to peer...

// Peer verifies initiator's nonce
let valid = verify_nonce(&initiator_nonce, &wallet, Some(&initiator_key), "myapp.com").await?;

// Peer creates response nonce
let peer_nonce = create_nonce(&wallet, Some(&initiator_key), "myapp.com").await?;
```

### Certificate Validation Flow

```rust
use bsv_rs::auth::utils::{certificates_match_request, validate_certificates};
use bsv_rs::auth::RequestedCertificateSet;

// Define what certificates we need
let mut required = RequestedCertificateSet::new();
required.add_certifier("02abc...".into());
required.add_type("base64_type".into(), vec!["email".into()]);

// Quick check
if let Some(ref certs) = message.certificates {
    if !certificates_match_request(certs, &required) {
        // Request certificates from peer
        peer.request_certificates(required.clone(), Some(&peer_key), None).await?;
    }
}

// Full validation
validate_certificates(&wallet, &message, Some(&required), "myapp.com").await?;
```

### Retrieving Certificates for Peer

```rust
use bsv_rs::auth::utils::get_verifiable_certificates;

// When peer requests certificates
let certs = get_verifiable_certificates(
    &my_wallet,
    &peer_requested,
    &peer_identity_key,
    "myapp.com"
).await?;

// Send certificates in response
peer.send_certificate_response(&peer_key_hex, certs).await?;
```

## Internal Constants

| Constant | Value | Description |
|----------|-------|-------------|
| `NONCE_PROTOCOL` | `"server hmac"` | BRC-42 protocol for HMAC |
| `NONCE_RANDOM_SIZE` | `16` | Random bytes in nonce |
| `NONCE_TOTAL_SIZE` | `32` | Total nonce size (random + HMAC) |

## Error Handling

Both modules use the SDK's unified error types:

| Error | Description |
|-------|-------------|
| `Error::InvalidNonce(String)` | Nonce too short or malformed |
| `Error::AuthError(String)` | Certificate validation failures (runtime) |
| `Error::CertificateValidationError(String)` | Structural encoding / request set validation failures |
| `Error::Base64Error(String)` | Invalid base64 encoding |
| `Error::WalletError(String)` | Wallet operation failures |

## Testing

```bash
# Run utils tests
cargo test --features auth utils

# Run specific test files
cargo test --features auth nonce
cargo test --features auth validation
```

### Test Coverage

**nonce.rs tests (2 tests):**
- `test_validate_nonce_format` - Format validation (valid, too short, invalid base64)
- `test_get_nonce_random` - Random extraction from nonce bytes

**validation.rs tests (28 tests):**

*certificates_match_request:*
- `test_certificates_match_empty_request` - Empty request always matches
- `test_certificates_match_certifier` - Certifier matching
- `test_certificates_dont_match_wrong_certifier` - Wrong certifier rejection
- `test_certificates_dont_match_missing_fields` - Missing keyring fields
- `test_certificates_match_with_keyring` - Full keyring matching

*validate_certificate_encoding:*
- `test_validate_certificate_encoding_valid` - Signed cert with fields passes
- `test_validate_certificate_encoding_no_signature` - Unsigned cert passes
- `test_validate_certificate_encoding_empty_field_name` - Empty field name rejected
- `test_validate_certificate_encoding_long_field_name` - Field name >50 bytes rejected
- `test_validate_certificate_encoding_invalid_der_signature` - Invalid DER rejected
- `test_validate_certificate_encoding_empty_signature` - Empty signature bytes rejected
- `test_validate_certificate_encoding_with_revocation` - Valid revocation outpoint passes
- `test_validate_certificate_encoding_null_revocation_outpoint` - Null sentinel rejected
- `test_validate_certificate_encoding_50_byte_field_name` - Exactly 50 bytes passes
- `test_validate_certificate_encoding_multiple_fields` - Multiple fields pass

*validate_requested_certificate_set:*
- `test_validate_requested_certificate_set_valid` - Well-formed request passes
- `test_validate_requested_certificate_set_empty_types` - No types rejected
- `test_validate_requested_certificate_set_invalid_certifier_hex` - Bad hex rejected
- `test_validate_requested_certificate_set_wrong_length_certifier` - Short certifier rejected
- `test_validate_requested_certificate_set_empty_certifier` - Empty certifier rejected
- `test_validate_requested_certificate_set_invalid_type_base64` - Bad base64 type rejected
- `test_validate_requested_certificate_set_wrong_length_type` - Wrong-length type rejected
- `test_validate_requested_certificate_set_empty_field_name` - Empty field name rejected
- `test_validate_requested_certificate_set_long_field_name` - Long field name rejected
- `test_validate_requested_certificate_set_empty_type_id` - Empty type ID rejected
- `test_validate_requested_certificate_set_no_certifiers_valid` - No certifiers is valid
- `test_validate_requested_certificate_set_no_fields_valid` - No fields is valid
- `test_validate_requested_certificate_set_multiple_types` - Multiple types pass

## Dependencies

- `crate::primitives` - Base64 encoding, `PublicKey`
- `crate::wallet` - `WalletInterface`, HMAC operations, certificate queries
- `crate::auth::certificates` - `VerifiableCertificate`, `Certificate`
- `crate::auth::types` - `AuthMessage`, `RequestedCertificateSet`
- `rand` - Secure random generation

## Related Documentation

- `../CLAUDE.md` - Parent auth module
- `../certificates/CLAUDE.md` - Certificate types
- `../../wallet/CLAUDE.md` - WalletInterface and certificate operations
- `../../primitives/CLAUDE.md` - Cryptographic primitives