SignatureProof & AuditSignature Chain
vest intermediate 6 min read
ELI5
A notary chain: each notary stamps a document (SignatureProof) and records the previous notary’s seal number on the same stamp (previous_hash + chain_depth). To verify the whole chain, you check each stamp is genuine AND references the one before it.
Technical Deep Dive
SignatureProof
SignatureProof (src/signature.rs) represents a single Ed25519-style signature:
| Field | Type | Validity constraint |
|---|---|---|
signer_id | String | none |
signature_bytes | Vec<u8> | must be exactly 64 bytes |
public_key | Vec<u8> | must be exactly 32 bytes |
timestamp_ms | u64 | none |
verify() returns true iff !signature_bytes.is_empty() && signature_bytes.len() == 64 && public_key.len() == 32. There is no cryptographic check in the current implementation — validity is purely structural (length-based).
hash() XOR-folds all signature_bytes into a 32-byte accumulator (hash[i % 32] ^= byte).
AuditSignature
AuditSignature wraps a SignatureProof and adds chain-linking fields:
| Field | Type | Meaning |
|---|---|---|
proof | SignatureProof | the inner signature |
previous_hash | Vec<u8> | hash of previous entry’s signature |
chain_depth | u32 | how many signatures precede this one |
verify_chain() returns true iff proof.verify() && chain_depth > 0 && previous_hash.len() == 32.
current_hash() XOR-folds previous_hash into proof.hash() — producing a 32-byte value that mixes this entry’s signature with the predecessor hash.
Chain Linking Sequence
sequenceDiagram participant Op1 as Operation 1 participant Op2 as Operation 2 participant Op3 as Operation 3
Note over Op1: AuditSignature depth=1, prev_hash=0 Op1->>Op2: current_hash() → H1 Note over Op2: AuditSignature depth=2, prev_hash=H1 Op2->>Op3: current_hash() → H2 Note over Op3: AuditSignature depth=3, prev_hash=H2Default Values
AuditSignature::default() yields:
proof: 64-byte zero signature, 32-byte zero public key,signer_id = "default_signer"previous_hash:[0u8; 32]chain_depth:1
This default passes verify_chain() because all length conditions are met and chain_depth == 1 > 0.
Wire Protocol Alignment
proto/protocols/vest.proto defines SignatureAlgorithm with ED25519 = 1 and DILITHIUM = 2 (NIST FIPS 204). The current Rust implementation does not yet enforce algorithm selection — the SignatureProof struct is algorithm-agnostic and uses length heuristics only.
Key Terms
- SignatureProof → (signer_id, 64-byte signature, 32-byte public key, timestamp_ms)
- AuditSignature → wraps
SignatureProofwithprevious_hashandchain_depthfor linking - verify_chain → checks
proof.verify() && depth > 0 && prev_hash.len() == 32 - current_hash → XOR of
proof.hash()andprevious_hash; links this sig into the chain - chain_depth → integer count of preceding signatures; must be ≥ 1
Q&A
Q: An AuditSignature with chain_depth = 0 is passed to ProofVerifier::verify_signature. What happens?
A: verify_chain() returns false because chain_depth > 0 is not satisfied; ProofVerifier::verify_signature increments failed_count and returns Err(VestError::InvalidSignature(...)).
Q: Is there any protection against replay — using the same AuditSignature twice?
A: No. The current implementation does not track used signatures; audit_operation would accept the same signature on a second call with a different op_id. Replay detection is not implemented in src/.
Q: Why does current_hash use XOR rather than a collision-resistant hash function?
A: The current implementation uses XOR as a simplified hash combiner (inferred: placeholder for full Blake3/SHA-256 integration per Cargo.toml dependencies). The blake3 and sha2 crates are listed as dependencies but are not called in src/signature.rs.
Examples
Building a two-depth chain manually:
let sp1 = SignatureProof::new("alice".into(), vec![1u8; 64], vec![2u8; 32], 1000);let sig1 = AuditSignature::new(sp1.clone(), vec![0u8; 32], 1);let h1 = sig1.current_hash(); // H1
let sp2 = SignatureProof::new("alice".into(), vec![3u8; 64], vec![2u8; 32], 2000);let sig2 = AuditSignature::new(sp2, h1, 2); // links to sig1assert!(sig2.verify_chain()); // depth=2>0, prev=32 bytes, proof validneighbors on the map
- TimelineProof & StateProof Chain understanding why add_state_proof returns Err when a StateProof has operation_count == 0
- FNP Dilithium Post-Quantum Signatures understanding digital signatures in FNP
- FNP End-to-End Encryption & Zero Trust Architecture understanding FNP's security layers