# 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
| `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:
| `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};
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`):
| 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
| `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
| `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