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
# BSV Module
> BSV-specific cryptographic operations for transaction signing and key management

## Overview

This module provides BSV blockchain-specific operations including transaction signature hash computation (BIP-143 style), transaction signatures with sighash scope, Schnorr zero-knowledge proofs for ECDH verification, and Shamir Secret Sharing for private key backup and recovery. All implementations maintain byte-for-byte compatibility with the TypeScript and Go BSV SDKs.

## Files

| File | Lines | Purpose |
|------|-------|---------|
| `mod.rs` | 75 | Module declarations and re-exports |
| `sighash.rs` | 623 | BIP-143 style sighash computation for transaction signing |
| `tx_signature.rs` | 309 | Transaction signatures with sighash scope (checksig format) |
| `schnorr.rs` | 413 | Schnorr ZK proofs for ECDH shared secret verification |
| `polynomial.rs` | 394 | Polynomial operations for Lagrange interpolation |
| `shamir.rs` | 1023 | Shamir Secret Sharing for private key backup |

## Key Exports

### Sighash Constants

```rust
pub const SIGHASH_ALL: u32 = 0x01;          // Sign all inputs and outputs
pub const SIGHASH_NONE: u32 = 0x02;         // Sign all inputs, no outputs
pub const SIGHASH_SINGLE: u32 = 0x03;       // Sign all inputs, matching output only
pub const SIGHASH_FORKID: u32 = 0x40;       // BSV-specific BIP-143 flag
pub const SIGHASH_ANYONECANPAY: u32 = 0x80; // Sign only this input
```

### Sighash Computation

```rust
/// Parameters for computing a sighash (derives Debug)
pub struct SighashParams<'a> {
    pub version: i32,
    pub inputs: &'a [TxInput],
    pub outputs: &'a [TxOutput],
    pub locktime: u32,
    pub input_index: usize,
    pub subscript: &'a [u8],
    pub satoshis: u64,
    pub scope: u32,
}

/// Compute sighash (returns display order, big-endian)
pub fn compute_sighash(params: &SighashParams) -> [u8; 32]

/// Compute sighash for ECDSA signing (returns internal order, little-endian)
pub fn compute_sighash_for_signing(params: &SighashParams) -> [u8; 32]

/// Build the BIP-143 preimage (the message to be hashed)
pub fn build_sighash_preimage(params: &SighashParams) -> Vec<u8>

/// Convenience: parse raw tx and compute sighash in one call
pub fn compute_sighash_from_raw(
    raw_tx: &[u8],
    input_index: usize,
    subscript: &[u8],
    satoshis: u64,
    scope: u32,
) -> Result<[u8; 32]>
```

### Transaction Structures

```rust
/// A transaction input (derives Debug, Clone)
pub struct TxInput {
    pub txid: [u8; 32],      // Previous tx ID (internal byte order)
    pub output_index: u32,
    pub script: Vec<u8>,      // scriptSig
    pub sequence: u32,
}

/// A transaction output (derives Debug, Clone)
pub struct TxOutput {
    pub satoshis: u64,
    pub script: Vec<u8>,      // scriptPubKey
}

/// A parsed raw transaction (derives Debug, Clone)
pub struct RawTransaction {
    pub version: i32,
    pub inputs: Vec<TxInput>,
    pub outputs: Vec<TxOutput>,
    pub locktime: u32,
}

/// Parse a serialized transaction
pub fn parse_transaction(raw: &[u8]) -> Result<RawTransaction>
```

### Transaction Signature

```rust
/// ECDSA signature with sighash scope for OP_CHECKSIG
/// Derives Clone, PartialEq, Eq. Implements Debug (hex-formatted) and Display.
pub struct TransactionSignature {
    // Private fields: signature (Signature), scope (u32)
}

impl TransactionSignature {
    pub fn new(signature: Signature, scope: u32) -> Self
    pub fn from_components(r: [u8; 32], s: [u8; 32], scope: u32) -> Self
    pub fn from_checksig_format(data: &[u8]) -> Result<Self>  // Empty data → blank sig

    pub fn signature(&self) -> &Signature
    pub fn scope(&self) -> u32
    pub fn r(&self) -> &[u8; 32]
    pub fn s(&self) -> &[u8; 32]

    pub fn has_low_s(&self) -> bool           // BIP-62 check
    pub fn to_low_s(&self) -> Self            // Convert to low-S form

    pub fn to_checksig_format(&self) -> Vec<u8>  // DER || sighash_byte
    pub fn to_der(&self) -> Vec<u8>
    pub fn to_compact(&self) -> [u8; 64]         // R || S
}

// Display outputs hex-encoded checksig format
impl Display for TransactionSignature { ... }

// Debug outputs struct with hex-encoded R, S, and scope
impl Debug for TransactionSignature { ... }
```

**Blank signature handling**: `from_checksig_format(&[])` creates a placeholder signature with R=1, S=1, scope=1. This is used for unsigned inputs during transaction construction.

### Schnorr ZK Proofs

```rust
/// A Schnorr zero-knowledge proof (derives Clone, Debug)
pub struct SchnorrProof {
    pub r: PublicKey,       // R = r*G (nonce commitment)
    pub s_prime: PublicKey, // S' = r*B (blinded shared secret)
    pub z: BigNumber,       // z = r + e*a mod n (response)
}

/// Schnorr ZK proof generation and verification
pub struct Schnorr;

impl Schnorr {
    /// Generate proof of private key knowledge and ECDH computation
    pub fn generate_proof(
        a: &PrivateKey,      // Prover's private key
        big_a: &PublicKey,   // Prover's public key (a*G)
        big_b: &PublicKey,   // Other party's public key
        big_s: &PublicKey,   // Shared secret (a*B)
    ) -> Result<SchnorrProof>

    /// Verify a Schnorr proof
    pub fn verify_proof(
        big_a: &PublicKey,
        big_b: &PublicKey,
        big_s: &PublicKey,
        proof: &SchnorrProof,
    ) -> bool
}
```

**Verification algorithm**:
1. Recompute challenge `e = SHA256(A || B || S || S' || R) mod n` (Fiat-Shamir)
2. Check `z*G == R + e*A`
3. Check `z*B == S' + e*S`

All points serialized in 33-byte compressed format for the challenge hash.

### Shamir Secret Sharing

```rust
/// A collection of key shares for recovery (derives Clone, Debug)
pub struct KeyShares {
    pub points: Vec<PointInFiniteField>,
    pub threshold: usize,
    pub integrity: String,  // First 4 chars of base58(sha256(secret))
}

impl KeyShares {
    pub fn new(points: Vec<PointInFiniteField>, threshold: usize, integrity: String) -> Self
    pub fn from_backup_format(shares: &[String]) -> Result<Self>
    pub fn to_backup_format(&self) -> Vec<String>
    pub fn recover_private_key(&self) -> Result<PrivateKey>
}

/// Split a private key into shares
pub fn split_private_key(
    key: &PrivateKey,
    threshold: usize,  // Minimum shares needed (2..=255)
    total: usize,      // Total shares to generate (>= threshold)
) -> Result<KeyShares>
```

**Parameter constraints**:
- `threshold` must be >= 2 (1-of-N is just copying the secret)
- `threshold` must be <= 255
- `total` must be >= `threshold`

**Recovery validation**:
- `from_backup_format` validates that all shares have matching threshold and integrity values
- `recover_private_key` checks that `points.len() >= threshold` before interpolation
- After recovery, the integrity checksum is verified against the recovered key

### Polynomial Operations

```rust
/// A point in a finite field (for Shamir shares)
/// Derives Clone, Debug, PartialEq, Eq. Implements Display (same as to_point_string).
pub struct PointInFiniteField {
    pub x: BigNumber,
    pub y: BigNumber,
}

impl PointInFiniteField {
    pub fn new(x: BigNumber, y: BigNumber) -> Self   // Reduces mod secp256k1 prime
    pub fn from_string(s: &str) -> Result<Self>       // "base58(x).base58(y)"
    pub fn to_point_string(&self) -> String
}

/// Polynomial for Lagrange interpolation (derives Clone, Debug)
pub struct Polynomial {
    pub points: Vec<PointInFiniteField>,
    pub threshold: usize,
}

impl Polynomial {
    pub fn new(points: Vec<PointInFiniteField>, threshold: usize) -> Self
    pub fn value_at(&self, x: &BigNumber) -> BigNumber  // Lagrange interpolation
}
```

**Lagrange interpolation formula**: `y = Σ(i=0..t-1) y_i * Π(j≠i) (x - x_j) / (x_i - x_j)`, all arithmetic mod secp256k1 prime. Uses modular inverse for division; returns zero if inverse fails (e.g., duplicate x coordinates).

## Internal Functions

These are not publicly exported but are key to understanding the implementation:

| Function | File | Purpose |
|----------|------|---------|
| `compute_hash_prevouts()` | sighash.rs | SHA256d of all input outpoints (zeros if ANYONECANPAY) |
| `compute_hash_sequence()` | sighash.rs | SHA256d of all input sequences (zeros if ANYONECANPAY/SINGLE/NONE) |
| `compute_hash_outputs()` | sighash.rs | SHA256d of outputs (all, single, or zeros depending on scope) |
| `compute_challenge()` | schnorr.rs | Fiat-Shamir challenge: `SHA256(A\|\|B\|\|S\|\|S'\|\|R) mod n` |
| `evaluate_polynomial()` | shamir.rs | Horner's method polynomial evaluation mod prime |
| `compute_integrity()` | shamir.rs | First 4 chars of `base58(sha256(key_bytes))` |
| `decode_share()` | shamir.rs | Parse backup format string into point + threshold + integrity |

## Usage

### Computing a Sighash

```rust
use bsv_rs::primitives::bsv::sighash::{
    compute_sighash, parse_transaction, SighashParams,
    SIGHASH_ALL, SIGHASH_FORKID,
};

// Parse raw transaction bytes
let raw_tx = hex::decode("0100000001...").unwrap();
let tx = parse_transaction(&raw_tx).unwrap();

// The subscript is typically the scriptPubKey of the UTXO being spent
let subscript = hex::decode("76a914...88ac").unwrap();

// Compute sighash for input 0
let sighash = compute_sighash(&SighashParams {
    version: tx.version,
    inputs: &tx.inputs,
    outputs: &tx.outputs,
    locktime: tx.locktime,
    input_index: 0,
    subscript: &subscript,
    satoshis: 100_000,  // Value of the UTXO being spent
    scope: SIGHASH_ALL | SIGHASH_FORKID,
});
```

### Creating and Parsing Transaction Signatures

```rust
use bsv_rs::primitives::bsv::tx_signature::TransactionSignature;
use bsv_rs::primitives::bsv::sighash::{SIGHASH_ALL, SIGHASH_FORKID};

// Parse from checksig format (DER || sighash_byte)
let checksig_bytes = hex::decode("3044...41").unwrap();
let tx_sig = TransactionSignature::from_checksig_format(&checksig_bytes).unwrap();

// Check sighash type
let scope = tx_sig.scope();
if scope & 0xff == SIGHASH_ALL | SIGHASH_FORKID {
    println!("Standard BSV signature");
}

// Ensure low-S form (BIP-62 compliance)
let normalized = tx_sig.to_low_s();

// Encode back to checksig format for use in scripts
let encoded = normalized.to_checksig_format();

// Display as hex string
println!("{}", tx_sig);  // Outputs hex-encoded checksig format
```

### Proving ECDH Computation with Schnorr

```rust
use bsv_rs::primitives::ec::PrivateKey;
use bsv_rs::primitives::bsv::schnorr::Schnorr;

// Alice and Bob have key pairs
let alice = PrivateKey::random();
let bob = PrivateKey::random();

// Alice computes shared secret
let shared = alice.derive_shared_secret(&bob.public_key()).unwrap();

// Alice generates a proof she knows her private key and computed S correctly
let proof = Schnorr::generate_proof(
    &alice,
    &alice.public_key(),
    &bob.public_key(),
    &shared,
).unwrap();

// Anyone can verify without learning Alice's private key
let valid = Schnorr::verify_proof(
    &alice.public_key(),
    &bob.public_key(),
    &shared,
    &proof,
);
assert!(valid);
```

### Splitting and Recovering Private Keys

```rust
use bsv_rs::primitives::bsv::shamir::{split_private_key, KeyShares};
use bsv_rs::primitives::ec::PrivateKey;

// Generate a key to protect
let key = PrivateKey::random();

// Split into 5 shares with threshold of 3
let shares = split_private_key(&key, 3, 5).unwrap();

// Export to backup format for storage
let backup: Vec<String> = shares.to_backup_format();
// Each string: "base58(x).base58(y).threshold.integrity"

// Later, recover from any 3 shares
let subset = KeyShares::from_backup_format(&backup[0..3]).unwrap();
let recovered = subset.recover_private_key().unwrap();

assert_eq!(key.to_bytes(), recovered.to_bytes());
```

## BIP-143 Preimage Format

The sighash preimage follows this exact layout (used by `build_sighash_preimage`):

| Field | Size | Description |
|-------|------|-------------|
| nVersion | 4 bytes LE | Transaction version |
| hashPrevouts | 32 bytes | SHA256d of all outpoints (or zeros) |
| hashSequence | 32 bytes | SHA256d of all sequences (or zeros) |
| outpoint | 36 bytes | txid (32) + output index (4) of the input being signed |
| scriptCode | varint + N bytes | The subscript for signing |
| value | 8 bytes LE | Satoshi value of the input being spent |
| nSequence | 4 bytes LE | Sequence of the input being signed |
| hashOutputs | 32 bytes | SHA256d of outputs (all, single, or zeros) |
| nLocktime | 4 bytes LE | Transaction locktime |
| sighash type | 4 bytes LE | Sighash scope flags |

## Sighash Types Explained

| Type | Value | Description |
|------|-------|-------------|
| `SIGHASH_ALL` | 0x01 | Sign all inputs and all outputs (most common) |
| `SIGHASH_NONE` | 0x02 | Sign all inputs, allow anyone to modify outputs |
| `SIGHASH_SINGLE` | 0x03 | Sign all inputs, only the output at same index |
| `SIGHASH_FORKID` | 0x40 | BSV flag for BIP-143 style hashing (required) |
| `SIGHASH_ANYONECANPAY` | 0x80 | Sign only this input, allow others to add inputs |

Common combinations:
- `SIGHASH_ALL | SIGHASH_FORKID` (0x41) - Standard BSV signature
- `SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY` (0xC1) - Crowdfunding

Base type extraction uses `SIGHASH_BASE_MASK` (0x1F) internally.

## Backup Format

Shamir shares use this string format for storage:

```
base58(x).base58(y).threshold.integrity
```

Example: `2.8hKJ7vP...3.ABCD`

- `2`: x-coordinate (share index, starting at 1)
- `8hKJ7vP...`: y-coordinate (the share value in base58)
- `3`: threshold (minimum shares needed)
- `ABCD`: integrity checksum (first 4 chars of base58(sha256(secret)))

The integrity field allows verification that shares belong together and that recovery succeeded. `from_backup_format` validates that all shares have consistent threshold and integrity values.

## Security Notes

### Sighash
- Always include `SIGHASH_FORKID` for BSV transactions
- `compute_sighash` returns display order (reversed); use `compute_sighash_for_signing` for ECDSA
- `compute_sighash_from_raw` validates input_index is within range

### Transaction Signatures
- Always normalize to low-S form for BIP-62 compliance using `to_low_s()`
- Check with `has_low_s()` before broadcasting
- `from_checksig_format` re-exports sighash constants for convenience

### Schnorr Proofs
- Proofs are non-transferable (bound to specific public keys A and B)
- Uses Fiat-Shamir heuristic for non-interactive verification
- Challenge hash: `SHA256(A || B || S || S' || R) mod n` (5 compressed points, 165 bytes)
- Both parties can independently generate and verify proofs for the same shared secret

### Shamir Secret Sharing
- Threshold must be >= 2 and <= 255 (1-of-N is just copying)
- All arithmetic is modulo the secp256k1 field prime
- Polynomial coefficients generated from `PrivateKey::random()` for cryptographic randomness
- Store shares in separate secure locations
- Never store more than (threshold - 1) shares together
- Duplicate shares in recovery cause failure (Lagrange interpolation requires unique x-coordinates)
- Providing more shares than threshold works (only first `threshold` points are used)

## Testing

| File | Tests | Coverage |
|------|-------|----------|
| `sighash.rs` | 8 unit tests | Parsing, constants, hash component conditions, output modes |
| `tx_signature.rs` | 6 unit tests | Roundtrip, blank sig, scope values, low-S |
| `schnorr.rs` | 7 unit tests | Roundtrip, wrong inputs, mutual verification, deterministic challenge |
| `polynomial.rs` | 8 unit tests | Linear/quadratic/constant polys, large numbers, string roundtrip, invalid format |
| `shamir.rs` | 34 unit tests | Split/recover, backup format, edge cases, parameter validation, cross-SDK parity |

Cross-SDK sighash test vectors (500 vectors) are in `tests/vectors/` and exercised by integration tests.

## Related Documentation

- `../CLAUDE.md` - Primitives module documentation
- `../ec/CLAUDE.md` - Elliptic curve operations (PrivateKey, PublicKey, Signature)
- `/CLAUDE.md` - Root SDK documentation