triton_vm/
challenges.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Challenges are needed for the [cross-table arguments](CrossTableArg), _i.e._,
//! [Permutation Arguments](air::cross_table_argument::PermArg),
//! [Evaluation Arguments](EvalArg), and
//! [Lookup Arguments](air::cross_table_argument::LookupArg),
//! as well as for the RAM Table's Contiguity Argument.
//!
//! There are three types of challenges:
//! - **Weights**. Weights are used to linearly combine multiple elements into one element. The
//!   resulting single element can then be used in a cross-table argument.
//! - **Indeterminates**. All cross-table arguments work by checking the equality of polynomials (or
//!   rational functions). Through the Schwartz-Zippel lemma, this equality check can be performed
//!   by evaluating the polynomials (or rational functions) in a single point. The challenges that
//!   are indeterminates are exactly this evaluation point. The polynomials (or rational functions)
//!   are never stored explicitly. Instead, they are directly evaluated at the point indicated by a
//!   challenge of “type” `Indeterminate`, giving rise to “running products”, “running
//!   evaluations”, _et cetera_.
//! - **Terminals**. The public input (respectively output) of the program is not stored in any
//!   table. Instead, the terminal of the Evaluation Argument is computed directly from the
//!   public input (respectively output) and the indeterminate.

use arbitrary::Arbitrary;
use std::ops::Index;
use std::ops::Range;
use std::ops::RangeInclusive;
use strum::EnumCount;
use twenty_first::math::tip5;
use twenty_first::prelude::*;

use air::challenge_id::ChallengeId;
use air::cross_table_argument::CrossTableArg;
use air::cross_table_argument::EvalArg;

use crate::prelude::Claim;

/// The `Challenges` struct holds the challenges used in Triton VM. The concrete
/// challenges are known only at runtime. The challenges are indexed using enum
/// [`ChallengeId`]. The `Challenges` struct is essentially a thin wrapper
/// around an array of [`XFieldElement`]s, providing convenience methods.
#[derive(Debug, Clone, Arbitrary)]
pub struct Challenges {
    pub challenges: [XFieldElement; Self::COUNT],
}

impl Challenges {
    /// The total number of challenges used in Triton VM.
    pub const COUNT: usize = ChallengeId::COUNT;

    /// The number of weights to sample using the Fiat-Shamir heuristic. This number is lower
    /// than the number of challenges because several challenges are not sampled, but computed
    /// from publicly known values and other, sampled challenges.
    ///
    /// Concretely:
    /// - The [`StandardInputTerminal`][ChallengeId::StandardInputTerminal] is computed
    ///   from Triton VM's public input and the sampled indeterminate
    ///   [`StandardInputIndeterminate`][ChallengeId::StandardInputIndeterminate].
    /// - The [`StandardOutputTerminal`][ChallengeId::StandardOutputTerminal] is computed
    ///   from Triton VM's public output and the sampled indeterminate
    ///   [`StandardOutputIndeterminate`][ChallengeId::StandardOutputIndeterminate].
    /// - The [`LookupTablePublicTerminal`][ChallengeId::LookupTablePublicTerminal] is
    ///   computed from the publicly known and constant lookup table and the sampled indeterminate
    ///   [`LookupTablePublicIndeterminate`][ChallengeId::LookupTablePublicIndeterminate].
    /// - The [`CompressedProgramDigest`][ChallengeId::CompressedProgramDigest] is computed
    ///   from the program to be executed and the sampled indeterminate
    ///   [`CompressProgramDigestIndeterminate`][ChallengeId::CompressProgramDigestIndeterminate].
    pub const SAMPLE_COUNT: usize = Self::COUNT - ChallengeId::NUM_DERIVED_CHALLENGES;

    pub fn new(mut challenges: Vec<XFieldElement>, claim: &Claim) -> Self {
        assert_eq!(Self::SAMPLE_COUNT, challenges.len());

        let compressed_digest = EvalArg::compute_terminal(
            &claim.program_digest.values(),
            EvalArg::default_initial(),
            challenges[ChallengeId::CompressProgramDigestIndeterminate.index()],
        );
        let input_terminal = EvalArg::compute_terminal(
            &claim.input,
            EvalArg::default_initial(),
            challenges[ChallengeId::StandardInputIndeterminate.index()],
        );
        let output_terminal = EvalArg::compute_terminal(
            &claim.output,
            EvalArg::default_initial(),
            challenges[ChallengeId::StandardOutputIndeterminate.index()],
        );
        let lookup_terminal = EvalArg::compute_terminal(
            &tip5::LOOKUP_TABLE.map(BFieldElement::from),
            EvalArg::default_initial(),
            challenges[ChallengeId::LookupTablePublicIndeterminate.index()],
        );

        challenges.insert(ChallengeId::StandardInputTerminal.index(), input_terminal);
        challenges.insert(ChallengeId::StandardOutputTerminal.index(), output_terminal);
        challenges.insert(
            ChallengeId::LookupTablePublicTerminal.index(),
            lookup_terminal,
        );
        challenges.insert(
            ChallengeId::CompressedProgramDigest.index(),
            compressed_digest,
        );
        assert_eq!(Self::COUNT, challenges.len());
        let challenges = challenges.try_into().unwrap();

        Self { challenges }
    }
}

impl Index<usize> for Challenges {
    type Output = XFieldElement;

    fn index(&self, id: usize) -> &Self::Output {
        &self.challenges[id]
    }
}

impl Index<Range<usize>> for Challenges {
    type Output = [XFieldElement];

    fn index(&self, indices: Range<usize>) -> &Self::Output {
        &self.challenges[indices.start..indices.end]
    }
}

impl Index<RangeInclusive<usize>> for Challenges {
    type Output = [XFieldElement];

    fn index(&self, indices: RangeInclusive<usize>) -> &Self::Output {
        &self.challenges[*indices.start()..=*indices.end()]
    }
}

impl Index<ChallengeId> for Challenges {
    type Output = XFieldElement;

    fn index(&self, id: ChallengeId) -> &Self::Output {
        &self[id.index()]
    }
}

impl Index<Range<ChallengeId>> for Challenges {
    type Output = [XFieldElement];

    fn index(&self, indices: Range<ChallengeId>) -> &Self::Output {
        &self[indices.start.index()..indices.end.index()]
    }
}

impl Index<RangeInclusive<ChallengeId>> for Challenges {
    type Output = [XFieldElement];

    fn index(&self, indices: RangeInclusive<ChallengeId>) -> &Self::Output {
        &self[indices.start().index()..=indices.end().index()]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::prelude::Claim;
    use twenty_first::xfe;

    // For testing purposes only.
    impl Default for Challenges {
        fn default() -> Self {
            Self::placeholder(&Claim::default())
        }
    }

    impl Challenges {
        /// Stand-in challenges for use in tests. For non-interactive STARKs, use the
        /// Fiat-Shamir heuristic to derive the actual challenges.
        pub fn placeholder(claim: &Claim) -> Self {
            let stand_in_challenges = (1..=Self::SAMPLE_COUNT)
                .map(|i| xfe!([42, i as u64, 24]))
                .collect();
            Self::new(stand_in_challenges, claim)
        }
    }

    #[test]
    fn various_challenge_indexing_operations_are_possible() {
        let challenges = Challenges::default();
        let _ = challenges[ChallengeId::StackWeight0];
        let _ = challenges[ChallengeId::StackWeight0..ChallengeId::StackWeight8];
        let _ = challenges[ChallengeId::StackWeight0..=ChallengeId::StackWeight8];
        let _ = challenges[0];
        let _ = challenges[0..8];
        let _ = challenges[0..=8];
    }
}