Skip to main content

csv_adapter_core/
proof_verify.rs

1//! Proof verification pipeline
2//!
3//! This module provides the core verification logic for proof bundles.
4
5use crate::error::{AdapterError, Result};
6use crate::proof::ProofBundle;
7use crate::signature::{verify_signatures, Signature, SignatureScheme};
8
9/// Verify a proof bundle according to the CSV verification pipeline.
10///
11/// The verification pipeline performs the following steps:
12/// 1. Validate deterministic VM execution of the DAG
13/// 2. Validate all authorizing signatures
14/// 3. Validate seal reference correctness (no double-use)
15/// 4. Validate inclusion proof against the anchor reference
16/// 5. Validate finality semantics
17///
18/// # Arguments
19/// * `bundle` - The proof bundle to verify
20/// * `seal_registry` - Set of already-used seals (for replay detection)
21/// * `signature_scheme` - The signature scheme to use for verification
22pub fn verify_proof(
23    bundle: &ProofBundle,
24    seal_registry: impl Fn(&[u8]) -> bool,
25    signature_scheme: SignatureScheme,
26) -> Result<()> {
27    // Step 1: Validate DAG structure
28    bundle
29        .transition_dag
30        .validate_structure()
31        .map_err(|e| AdapterError::Generic(format!("Invalid DAG structure: {}", e)))?;
32
33    // Step 2: Validate signatures with cryptographic verification
34    verify_bundle_signatures(bundle, signature_scheme)?;
35
36    // Step 3: Validate seal reference (check for replay)
37    if seal_registry(bundle.seal_ref.seal_id.as_ref()) {
38        return Err(AdapterError::SealReplay(format!(
39            "Seal {:?} has already been used",
40            bundle.seal_ref
41        )));
42    }
43
44    // Step 4: Validate inclusion proof (chain-specific, validated by adapter)
45    if bundle.inclusion_proof.proof_bytes.is_empty() {
46        return Err(AdapterError::InclusionProofFailed(
47            "Empty inclusion proof".to_string(),
48        ));
49    }
50
51    // Step 5: Validate finality (chain-specific, validated by adapter)
52    if bundle.finality_proof.confirmations == 0 {
53        return Err(AdapterError::FinalityNotReached(
54            "No confirmations yet".to_string(),
55        ));
56    }
57
58    Ok(())
59}
60
61/// Verify all signatures in a proof bundle
62///
63/// This function:
64/// 1. Extracts signatures from the bundle
65/// 2. Constructs signature verification contexts
66/// 3. Performs cryptographic verification
67fn verify_bundle_signatures(bundle: &ProofBundle, scheme: SignatureScheme) -> Result<()> {
68    // Check we have signatures
69    if bundle.signatures.is_empty() {
70        return Err(AdapterError::SignatureVerificationFailed(
71            "No signatures in proof bundle".to_string(),
72        ));
73    }
74
75    // For each signature in the bundle, verify it
76    // In a full implementation, each signature would have associated metadata
77    // (public key, signed message) encoded within it
78    //
79    // The signature format is:
80    // [public_key_length (4 bytes LE)] [public_key] [signature_bytes]
81    // The message is the DAG root commitment hash
82
83    let mut signatures = Vec::with_capacity(bundle.signatures.len());
84
85    for (i, sig_bytes) in bundle.signatures.iter().enumerate() {
86        // Parse signature format: [pk_len (4)] [public_key] [signature]
87        if sig_bytes.len() < 4 {
88            return Err(AdapterError::SignatureVerificationFailed(format!(
89                "Signature {} too short for header",
90                i
91            )));
92        }
93
94        // Extract public key length (little-endian u32)
95        let pk_len =
96            u32::from_le_bytes([sig_bytes[0], sig_bytes[1], sig_bytes[2], sig_bytes[3]]) as usize;
97
98        if sig_bytes.len() < 4 + pk_len {
99            return Err(AdapterError::SignatureVerificationFailed(format!(
100                "Signature {} too short for public key",
101                i
102            )));
103        }
104
105        let public_key = sig_bytes[4..4 + pk_len].to_vec();
106        let signature = sig_bytes[4 + pk_len..].to_vec();
107
108        // The signed message is the DAG root commitment
109        let message = bundle.transition_dag.root_commitment.as_bytes().to_vec();
110
111        signatures.push(Signature::new(signature, public_key, message));
112    }
113
114    // Verify all signatures
115    verify_signatures(&signatures, scheme)
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::dag::{DAGNode, DAGSegment};
122    use crate::hash::Hash;
123    use crate::proof::{FinalityProof, InclusionProof};
124    use crate::seal::{AnchorRef, SealRef};
125    use crate::signature::SignatureScheme;
126
127    fn make_secp256k1_signature_bytes(message: &[u8; 32]) -> Vec<u8> {
128        use secp256k1::{Message, Secp256k1, SecretKey};
129        let secp = Secp256k1::new();
130        let secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng());
131        let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
132        let msg = Message::from_digest_slice(message).unwrap();
133        let signature = secp.sign_ecdsa(&msg, &secret_key);
134        let sig_bytes = signature.serialize_compact();
135        let pubkey_bytes = public_key.serialize();
136        // Format: [pk_len (4 bytes LE)] [public_key] [signature]
137        let mut encoded = Vec::with_capacity(4 + pubkey_bytes.len() + sig_bytes.len());
138        encoded.extend_from_slice(&(pubkey_bytes.len() as u32).to_le_bytes());
139        encoded.extend_from_slice(&pubkey_bytes);
140        encoded.extend_from_slice(&sig_bytes);
141        encoded
142    }
143
144    fn make_ed25519_signature_bytes(message: &[u8]) -> Vec<u8> {
145        use ed25519_dalek::{Signer, SigningKey};
146        let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
147        let verifying_key = signing_key.verifying_key();
148        let signature = signing_key.sign(message);
149        // Format: [pk_len (4 bytes LE)] [public_key] [signature]
150        let mut encoded = Vec::with_capacity(4 + 32 + 64);
151        encoded.extend_from_slice(&32u32.to_le_bytes());
152        encoded.extend_from_slice(&verifying_key.to_bytes());
153        encoded.extend_from_slice(&signature.to_bytes());
154        encoded
155    }
156
157    fn test_bundle_with_signatures() -> Result<ProofBundle> {
158        // The message signed is the DAG root commitment (Hash::zero() = 32 zero bytes)
159        let message = [0u8; 32];
160        let signature = make_secp256k1_signature_bytes(&message);
161
162        let bundle = ProofBundle::new(
163            DAGSegment::new(
164                vec![DAGNode::new(
165                    Hash::new([1u8; 32]),
166                    vec![0x01, 0x02],
167                    vec![signature.clone()],
168                    vec![],
169                    vec![],
170                )],
171                Hash::zero(),
172            ),
173            vec![signature],
174            SealRef::new(vec![1, 2, 3], Some(42))
175                .map_err(|e| AdapterError::Generic(e.to_string()))?,
176            AnchorRef::new(vec![4, 5, 6], 100, vec![])
177                .map_err(|e| AdapterError::Generic(e.to_string()))?,
178            InclusionProof::new(vec![0xCD; 32], Hash::new([2u8; 32]), 0)
179                .map_err(|e| AdapterError::Generic(e.to_string()))?,
180            FinalityProof::new(vec![], 6, false)
181                .map_err(|e| AdapterError::Generic(e.to_string()))?,
182        )
183        .map_err(|e| AdapterError::Generic(e.to_string()))?;
184        Ok(bundle)
185    }
186
187    #[test]
188    fn test_verify_proof_valid() {
189        let bundle = test_bundle_with_signatures().unwrap();
190        let seal_registry = |_seal_id: &[u8]| false;
191        assert!(verify_proof(&bundle, seal_registry, SignatureScheme::Secp256k1).is_ok());
192    }
193
194    #[test]
195    fn test_verify_proof_seal_replay() {
196        let bundle = test_bundle_with_signatures().unwrap();
197        let seal_registry = |seal_id: &[u8]| seal_id == [1, 2, 3];
198        assert!(verify_proof(&bundle, seal_registry, SignatureScheme::Secp256k1).is_err());
199    }
200
201    #[test]
202    fn test_verify_proof_no_signatures() {
203        let mut bundle = test_bundle_with_signatures().unwrap();
204        bundle.signatures.clear();
205        let seal_registry = |_seal_id: &[u8]| false;
206        assert!(verify_proof(&bundle, seal_registry, SignatureScheme::Secp256k1).is_err());
207    }
208
209    #[test]
210    fn test_verify_proof_no_confirmations() {
211        let mut bundle = test_bundle_with_signatures().unwrap();
212        bundle.finality_proof.confirmations = 0;
213        let seal_registry = |_seal_id: &[u8]| false;
214        assert!(verify_proof(&bundle, seal_registry, SignatureScheme::Secp256k1).is_err());
215    }
216
217    #[test]
218    fn test_verify_proof_invalid_signature_format() {
219        let mut bundle = test_bundle_with_signatures().unwrap();
220        // Corrupt signature format
221        bundle.signatures[0] = vec![0x00, 0x00]; // Too short
222        let seal_registry = |_seal_id: &[u8]| false;
223        assert!(verify_proof(&bundle, seal_registry, SignatureScheme::Secp256k1).is_err());
224    }
225
226    #[test]
227    fn test_verify_proof_ed25519_valid_format() {
228        // The message signed is the DAG root commitment (Hash::zero() = 32 zero bytes)
229        let message = [0u8; 32];
230        let signature = make_ed25519_signature_bytes(&message);
231
232        let mut bundle = test_bundle_with_signatures().unwrap();
233        bundle.signatures = vec![signature];
234
235        let seal_registry = |_seal_id: &[u8]| false;
236        assert!(verify_proof(&bundle, seal_registry, SignatureScheme::Ed25519).is_ok());
237    }
238}