trident/field/proof.rs
1//! Universal STARK proof estimation and claim structure.
2//!
3//! These formulas apply to all FRI-based STARK provers regardless of
4//! target VM or field. Warriors call these functions for cost reporting
5//! and proof parameter computation.
6
7// ─── Claim ─────────────────────────────────────────────────────────
8
9/// Universal proof claim: what any STARK/SNARK proof asserts.
10///
11/// This is the public data shared between prover and verifier.
12/// The proof itself is warrior-specific (opaque bytes); the claim
13/// is universal.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Claim {
16 /// Hash of the compiled program (VM-native digest).
17 pub program_hash: Vec<u64>,
18 /// Public input field elements.
19 pub public_input: Vec<u64>,
20 /// Public output field elements.
21 pub public_output: Vec<u64>,
22}
23
24// ─── Trace Geometry ────────────────────────────────────────────────
25
26/// Padded trace height: next power of two above the tallest table.
27///
28/// Universal to all STARKs — the execution trace is padded to a power
29/// of two for NTT (Number Theoretic Transform) efficiency.
30pub fn padded_height(max_table_rows: u64) -> u64 {
31 if max_table_rows == 0 {
32 return 1;
33 }
34 max_table_rows.next_power_of_two()
35}
36
37/// Merkle tree depth for a trace of the given padded height.
38pub fn merkle_depth(padded_height: u64) -> u32 {
39 if padded_height <= 1 {
40 return 0;
41 }
42 64 - (padded_height - 1).leading_zeros()
43}
44
45/// NTT (Number Theoretic Transform) domain size.
46///
47/// The evaluation domain is `padded_height * blowup_factor`. Larger
48/// blowup factors give stronger soundness per FRI query but increase
49/// prover work linearly.
50pub fn ntt_domain_size(padded_height: u64, blowup_factor: u64) -> u64 {
51 padded_height.saturating_mul(blowup_factor)
52}
53
54// ─── FRI Parameters ────────────────────────────────────────────────
55
56/// Estimate the number of FRI queries needed for a target security level.
57///
58/// Each FRI query provides approximately `log2(blowup_factor)` bits of
59/// security against a dishonest prover. The total number of queries is
60/// `ceil(security_bits / log2(blowup_factor))`.
61///
62/// Typical parameters: security_bits=128, blowup_factor=4 → 64 queries.
63pub fn fri_query_count(security_bits: u32, blowup_factor: u64) -> u32 {
64 if blowup_factor <= 1 {
65 return security_bits;
66 }
67 // Integer log2 of blowup_factor
68 let log2_blowup = 63 - blowup_factor.leading_zeros();
69 if log2_blowup == 0 {
70 return security_bits;
71 }
72 (security_bits + log2_blowup - 1) / log2_blowup
73}
74
75// ─── Proof Size Estimation ─────────────────────────────────────────
76
77/// Estimate proof size in bytes.
78///
79/// A STARK proof contains:
80/// - FRI merkle authentication paths (one per query per column)
81/// - Polynomial evaluations at query points
82/// - FRI folding commitments
83///
84/// This gives a rough lower bound. Actual proofs include additional
85/// metadata, boundary constraints, and zero-knowledge blinding.
86pub fn estimate_proof_size(
87 padded_height: u64,
88 column_count: u64,
89 field_bytes: u64,
90 blowup_factor: u64,
91 security_bits: u32,
92) -> u64 {
93 let queries = fri_query_count(security_bits, blowup_factor) as u64;
94 let depth = merkle_depth(padded_height) as u64;
95 // Each query: column_count evaluations + Merkle path (depth * hash_size)
96 let hash_size: u64 = 32; // typical: 256-bit hash nodes
97 let per_query = column_count * field_bytes + depth * hash_size;
98 queries * per_query
99}
100
101/// Estimate proving time in nanoseconds.
102///
103/// Rough universal estimate based on NTT-dominated proving:
104/// `padded_height * column_count * log2(padded_height) * ns_per_field_op`.
105///
106/// The constant 3 ns/op is a conservative estimate for modern CPUs
107/// performing 64-bit field multiplication.
108pub fn estimate_proving_ns(padded_height: u64, column_count: u64) -> u64 {
109 if padded_height == 0 || column_count == 0 {
110 return 0;
111 }
112 let log_h = 64 - padded_height.leading_zeros() as u64;
113 padded_height
114 .saturating_mul(column_count)
115 .saturating_mul(log_h)
116 .saturating_mul(3)
117}
118
119// ─── Tests ─────────────────────────────────────────────────────────
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn padded_height_powers_of_two() {
127 assert_eq!(padded_height(0), 1);
128 assert_eq!(padded_height(1), 1);
129 assert_eq!(padded_height(2), 2);
130 assert_eq!(padded_height(3), 4);
131 assert_eq!(padded_height(5), 8);
132 assert_eq!(padded_height(1024), 1024);
133 assert_eq!(padded_height(1025), 2048);
134 }
135
136 #[test]
137 fn merkle_depth_correct() {
138 assert_eq!(merkle_depth(1), 0);
139 assert_eq!(merkle_depth(2), 1);
140 assert_eq!(merkle_depth(4), 2);
141 assert_eq!(merkle_depth(1024), 10);
142 assert_eq!(merkle_depth(1 << 20), 20);
143 }
144
145 #[test]
146 fn fri_queries_typical() {
147 // blowup=4 → log2=2 → 128/2 = 64 queries
148 assert_eq!(fri_query_count(128, 4), 64);
149 // blowup=8 → log2=3 → ceil(128/3) = 43 queries
150 assert_eq!(fri_query_count(128, 8), 43);
151 // blowup=2 → log2=1 → 128 queries
152 assert_eq!(fri_query_count(128, 2), 128);
153 // blowup=1 → degenerate
154 assert_eq!(fri_query_count(128, 1), 128);
155 }
156
157 #[test]
158 fn proof_size_reasonable() {
159 // Typical Triton: 2^20 padded, 200 columns, 8-byte field, blowup=4, 128-bit security
160 let size = estimate_proof_size(1 << 20, 200, 8, 4, 128);
161 // Should be in the range of hundreds of KB to low MB
162 assert!(size > 100_000, "proof too small: {}", size);
163 assert!(size < 100_000_000, "proof too large: {}", size);
164 }
165
166 #[test]
167 fn proving_time_nonzero() {
168 let ns = estimate_proving_ns(1 << 20, 200);
169 assert!(ns > 0);
170 // Should be in the range of seconds (billions of ns)
171 assert!(ns > 1_000_000_000, "estimate too fast: {} ns", ns);
172 }
173
174 #[test]
175 fn proving_time_zero_inputs() {
176 assert_eq!(estimate_proving_ns(0, 100), 0);
177 assert_eq!(estimate_proving_ns(100, 0), 0);
178 }
179}