triton_vm/challenges.rs
1//! Challenges are needed for the [cross-table arguments](CrossTableArg),
2//! _i.e._, [Permutation Arguments](air::cross_table_argument::PermArg),
3//! [Evaluation Arguments](EvalArg), and
4//! [Lookup Arguments](air::cross_table_argument::LookupArg),
5//! as well as for the RAM Table's Contiguity Argument.
6//!
7//! There are three types of challenges:
8//! - **Weights**. Weights are used to linearly combine multiple elements into
9//! one element. The resulting single element can then be used in a
10//! cross-table argument.
11//! - **Indeterminates**. All cross-table arguments work by checking the
12//! equality of polynomials (or rational functions). Through the
13//! Schwartz-Zippel lemma, this equality check can be performed by evaluating
14//! the polynomials (or rational functions) in a single point. The challenges
15//! that are indeterminates are exactly this evaluation point. The polynomials
16//! (or rational functions) are never stored explicitly. Instead, they are
17//! directly evaluated at the point indicated by a challenge of “type”
18//! `Indeterminate`, giving rise to “running products”, “running evaluations”,
19//! _et cetera_.
20//! - **Terminals**. The public input (respectively output) of the program is
21//! not stored in any table. Instead, the terminal of the Evaluation Argument
22//! is computed directly from the public input (respectively output) and the
23//! indeterminate.
24
25use arbitrary::Arbitrary;
26use std::ops::Index;
27use std::ops::Range;
28use std::ops::RangeInclusive;
29use strum::EnumCount;
30use twenty_first::prelude::*;
31use twenty_first::tip5;
32
33use air::challenge_id::ChallengeId;
34use air::cross_table_argument::CrossTableArg;
35use air::cross_table_argument::EvalArg;
36
37use crate::prelude::Claim;
38
39/// The `Challenges` struct holds the challenges used in Triton VM. The concrete
40/// challenges are known only at runtime. The challenges are indexed using enum
41/// [`ChallengeId`]. The `Challenges` struct is essentially a thin wrapper
42/// around an array of [`XFieldElement`]s, providing convenience methods.
43#[derive(Debug, Clone, Arbitrary)]
44pub struct Challenges {
45 pub challenges: [XFieldElement; Self::COUNT],
46}
47
48impl Challenges {
49 /// The total number of challenges used in Triton VM.
50 pub const COUNT: usize = ChallengeId::COUNT;
51
52 /// The number of weights to sample using the Fiat-Shamir heuristic. This
53 /// number is lower than the number of challenges because several
54 /// challenges are not sampled, but computed from publicly known values
55 /// and other, sampled challenges.
56 ///
57 /// Concretely:
58 /// - The [`StandardInputTerminal`][std_in_term] is computed from Triton
59 /// VM's public input and the sampled indeterminate
60 /// [`StandardInputIndeterminate`][std_in_ind].
61 /// - The [`StandardOutputTerminal`][std_out_term] is computed from Triton
62 /// VM's public output and the sampled indeterminate
63 /// [`StandardOutputIndeterminate`][std_out_ind].
64 /// - The [`LookupTablePublicTerminal`][lk_up_term] is computed from the
65 /// publicly known and constant lookup table and the sampled indeterminate
66 /// [`LookupTablePublicIndeterminate`][lk_up_ind].
67 /// - The [`CompressedProgramDigest`][program_digest] is computed from the
68 /// program to be executed and the sampled indeterminate
69 /// [`CompressProgramDigestIndeterminate`][program_digest_ind].
70 ///
71 /// [std_in_term]: ChallengeId::StandardInputTerminal
72 /// [std_in_ind]: ChallengeId::StandardInputIndeterminate
73 /// [std_out_term]: ChallengeId::StandardOutputTerminal
74 /// [std_out_ind]: ChallengeId::StandardOutputIndeterminate
75 /// [lk_up_term]: ChallengeId::LookupTablePublicTerminal
76 /// [lk_up_ind]: ChallengeId::LookupTablePublicIndeterminate
77 /// [program_digest]: ChallengeId::CompressedProgramDigest
78 /// [program_digest_ind]: ChallengeId::CompressProgramDigestIndeterminate
79 pub const SAMPLE_COUNT: usize = Self::COUNT - ChallengeId::NUM_DERIVED_CHALLENGES;
80
81 pub fn new(mut challenges: Vec<XFieldElement>, claim: &Claim) -> Self {
82 assert_eq!(Self::SAMPLE_COUNT, challenges.len());
83
84 let compressed_digest = EvalArg::compute_terminal(
85 &claim.program_digest.values(),
86 EvalArg::default_initial(),
87 challenges[ChallengeId::CompressProgramDigestIndeterminate.index()],
88 );
89 let input_terminal = EvalArg::compute_terminal(
90 &claim.input,
91 EvalArg::default_initial(),
92 challenges[ChallengeId::StandardInputIndeterminate.index()],
93 );
94 let output_terminal = EvalArg::compute_terminal(
95 &claim.output,
96 EvalArg::default_initial(),
97 challenges[ChallengeId::StandardOutputIndeterminate.index()],
98 );
99 let lookup_terminal = EvalArg::compute_terminal(
100 &tip5::LOOKUP_TABLE.map(BFieldElement::from),
101 EvalArg::default_initial(),
102 challenges[ChallengeId::LookupTablePublicIndeterminate.index()],
103 );
104
105 challenges.insert(ChallengeId::StandardInputTerminal.index(), input_terminal);
106 challenges.insert(ChallengeId::StandardOutputTerminal.index(), output_terminal);
107 challenges.insert(
108 ChallengeId::LookupTablePublicTerminal.index(),
109 lookup_terminal,
110 );
111 challenges.insert(
112 ChallengeId::CompressedProgramDigest.index(),
113 compressed_digest,
114 );
115 assert_eq!(Self::COUNT, challenges.len());
116 let challenges = challenges.try_into().unwrap();
117
118 Self { challenges }
119 }
120}
121
122impl Index<usize> for Challenges {
123 type Output = XFieldElement;
124
125 fn index(&self, id: usize) -> &Self::Output {
126 &self.challenges[id]
127 }
128}
129
130impl Index<Range<usize>> for Challenges {
131 type Output = [XFieldElement];
132
133 fn index(&self, indices: Range<usize>) -> &Self::Output {
134 &self.challenges[indices.start..indices.end]
135 }
136}
137
138impl Index<RangeInclusive<usize>> for Challenges {
139 type Output = [XFieldElement];
140
141 fn index(&self, indices: RangeInclusive<usize>) -> &Self::Output {
142 &self.challenges[*indices.start()..=*indices.end()]
143 }
144}
145
146impl Index<ChallengeId> for Challenges {
147 type Output = XFieldElement;
148
149 fn index(&self, id: ChallengeId) -> &Self::Output {
150 &self[id.index()]
151 }
152}
153
154impl Index<Range<ChallengeId>> for Challenges {
155 type Output = [XFieldElement];
156
157 fn index(&self, indices: Range<ChallengeId>) -> &Self::Output {
158 &self[indices.start.index()..indices.end.index()]
159 }
160}
161
162impl Index<RangeInclusive<ChallengeId>> for Challenges {
163 type Output = [XFieldElement];
164
165 fn index(&self, indices: RangeInclusive<ChallengeId>) -> &Self::Output {
166 &self[indices.start().index()..=indices.end().index()]
167 }
168}
169
170#[cfg(test)]
171#[cfg_attr(coverage_nightly, coverage(off))]
172mod tests {
173 use super::*;
174 use crate::prelude::Claim;
175 use twenty_first::xfe;
176
177 // For testing purposes only.
178 impl Default for Challenges {
179 fn default() -> Self {
180 Self::placeholder(&Claim::default())
181 }
182 }
183
184 impl Challenges {
185 /// Stand-in challenges for use in tests. For non-interactive STARKs,
186 /// use the Fiat-Shamir heuristic to derive the actual
187 /// challenges.
188 pub fn placeholder(claim: &Claim) -> Self {
189 let stand_in_challenges = (1..=Self::SAMPLE_COUNT)
190 .map(|i| xfe!([42, i as u64, 24]))
191 .collect();
192 Self::new(stand_in_challenges, claim)
193 }
194 }
195
196 #[test]
197 fn various_challenge_indexing_operations_are_possible() {
198 let challenges = Challenges::default();
199 let _ = challenges[ChallengeId::StackWeight0];
200 let _ = challenges[ChallengeId::StackWeight0..ChallengeId::StackWeight8];
201 let _ = challenges[ChallengeId::StackWeight0..=ChallengeId::StackWeight8];
202 let _ = challenges[0];
203 let _ = challenges[0..8];
204 let _ = challenges[0..=8];
205 }
206}