cross_language_verify/
verify.rs1use std::env;
24use std::fs;
25use std::path::PathBuf;
26use std::process::ExitCode;
27
28use serde::Deserialize;
29use web4_core::{Ledger, LocalLedger};
30
31#[derive(Deserialize)]
32struct LctSidecar {
33 lct_id: String,
34 fingerprint: String,
35}
36
37fn parse_args() -> (PathBuf, PathBuf) {
38 let mut ledger = PathBuf::from("./shared_ledger.jsonl");
39 let mut sidecar = PathBuf::from("./shared_lct.json");
40 let mut args = env::args().skip(1);
41 while let Some(arg) = args.next() {
42 match arg.as_str() {
43 "--ledger" => {
44 ledger = args
45 .next()
46 .expect("--ledger requires a path argument")
47 .into();
48 }
49 "--lct-sidecar" => {
50 sidecar = args
51 .next()
52 .expect("--lct-sidecar requires a path argument")
53 .into();
54 }
55 "-h" | "--help" => {
56 eprintln!(
57 "usage: cargo run --example cross_language_verify -- \
58 [--ledger PATH] [--lct-sidecar PATH]"
59 );
60 std::process::exit(0);
61 }
62 other => {
63 eprintln!("unknown argument: {}", other);
64 std::process::exit(2);
65 }
66 }
67 }
68 (ledger, sidecar)
69}
70
71fn main() -> ExitCode {
72 let (ledger_path, sidecar_path) = parse_args();
73
74 println!("[rust] Reading sidecar at {}", sidecar_path.display());
75 let sidecar_text = match fs::read_to_string(&sidecar_path) {
76 Ok(s) => s,
77 Err(e) => {
78 eprintln!("[rust] Could not read sidecar {}: {}", sidecar_path.display(), e);
79 eprintln!("[rust] Did you run `python mint.py` first?");
80 return ExitCode::from(2);
81 }
82 };
83 let sidecar: LctSidecar = match serde_json::from_str(&sidecar_text) {
84 Ok(s) => s,
85 Err(e) => {
86 eprintln!("[rust] Sidecar parse error: {}", e);
87 return ExitCode::from(2);
88 }
89 };
90 println!("[rust] lct_id = {}", sidecar.lct_id);
91 println!("[rust] fingerprint = {}", sidecar.fingerprint);
92
93 println!(
94 "[rust] Opening LocalLedger at {} (this replays + verifies the chain)",
95 ledger_path.display()
96 );
97 let ledger = match LocalLedger::open(&ledger_path) {
98 Ok(l) => l,
99 Err(e) => {
100 eprintln!("[rust] Ledger open failed: {}", e);
101 eprintln!("[rust] If this is a chain-integrity failure, the ledger was tampered.");
102 return ExitCode::from(3);
103 }
104 };
105 println!("[rust] Chain intact. {} entries replayed.", ledger.len());
106
107 let lct_uuid = match uuid::Uuid::parse_str(&sidecar.lct_id) {
108 Ok(u) => u,
109 Err(e) => {
110 eprintln!("[rust] Sidecar lct_id is not a UUID: {}", e);
111 return ExitCode::from(2);
112 }
113 };
114
115 let lookup = match ledger.lookup(lct_uuid) {
116 Ok(l) => l,
117 Err(e) => {
118 eprintln!("[rust] Ledger lookup failed: {}", e);
119 return ExitCode::from(3);
120 }
121 };
122 let lct = match lookup {
123 Some(lct) => lct,
124 None => {
125 eprintln!("[rust] LCT {} not present in ledger.", sidecar.lct_id);
126 return ExitCode::from(3);
127 }
128 };
129 println!("[rust] LCT present in ledger.");
130
131 if lct.fingerprint() != sidecar.fingerprint {
132 eprintln!(
133 "[rust] FINGERPRINT MISMATCH:\n sidecar: {}\n on-chain: {}",
134 sidecar.fingerprint,
135 lct.fingerprint()
136 );
137 return ExitCode::from(3);
138 }
139 println!("[rust] Fingerprint matches sidecar.");
140
141 let proof = match ledger.anchor(lct.id) {
142 Ok(p) => p,
143 Err(e) => {
144 eprintln!("[rust] anchor() failed: {}", e);
145 return ExitCode::from(3);
146 }
147 };
148 let verified = match ledger.verify_proof(&proof) {
149 Ok(v) => v,
150 Err(e) => {
151 eprintln!("[rust] verify_proof error: {}", e);
152 return ExitCode::from(3);
153 }
154 };
155 if !verified {
156 eprintln!("[rust] Anchor proof DID NOT verify under the Rust implementation.");
157 return ExitCode::from(3);
158 }
159 println!("[rust] Anchor proof verifies under the Rust implementation. ✓");
160 println!();
161 println!("[rust] Cross-language verification succeeded.");
162 println!(
163 "[rust] Python wrote a hash-chained ledger; Rust read the same file, replayed the chain, \
164 looked up the LCT by id, matched its fingerprint, and verified the inclusion proof — \
165 with zero shared runtime. The on-disk format is the contract."
166 );
167 ExitCode::SUCCESS
168}