p3_challenger/
grinding_challenger.rs

1use p3_field::{Field, PrimeField, PrimeField32, PrimeField64};
2use p3_maybe_rayon::prelude::*;
3use p3_symmetric::CryptographicPermutation;
4use tracing::instrument;
5
6use crate::{
7    CanObserve, CanSampleBits, CanSampleUniformBits, DuplexChallenger, MultiField32Challenger,
8    UniformSamplingField,
9};
10
11/// Trait for challengers that support proof-of-work (PoW) grinding.
12///
13/// A `GrindingChallenger` can:
14/// - Absorb a candidate witness into the transcript
15/// - Sample random bitstrings to check the PoW condition
16/// - Brute-force search for a valid witness that satisfies the PoW
17///
18/// This trait is typically used in protocols requiring computational effort
19/// from the prover.
20pub trait GrindingChallenger:
21    CanObserve<Self::Witness> + CanSampleBits<usize> + Sync + Clone
22{
23    /// The underlying field element type used as the witness.
24    type Witness: Field;
25
26    /// Perform a brute-force search to find a valid PoW witness.
27    ///
28    /// Given a `bits` parameter, this function searches for a field element
29    /// `witness` such that after observing it, the next `bits` bits that challenger outputs
30    /// are all `0`.
31    fn grind(&mut self, bits: usize) -> Self::Witness;
32
33    /// Check whether a given `witness` satisfies the PoW condition.
34    ///
35    /// After absorbing the witness, the challenger samples `bits` random bits
36    /// and verifies that all bits sampled are zero.
37    ///
38    /// Returns `true` if the witness passes the PoW check, `false` otherwise.
39    #[must_use]
40    fn check_witness(&mut self, bits: usize, witness: Self::Witness) -> bool {
41        if bits == 0 {
42            return true;
43        }
44        self.observe(witness);
45        self.sample_bits(bits) == 0
46    }
47}
48
49/// Trait for challengers that support proof-of-work (PoW) grinding with
50/// guaranteed uniformly sampled bits.
51pub trait UniformGrindingChallenger:
52    GrindingChallenger + CanSampleUniformBits<Self::Witness>
53{
54    /// Grinds based on *uniformly sampled bits*. This variant is allowed to do rejection
55    /// sampling if a value is sampled that would violate our uniformity requirement
56    /// (chance of about 1/P).
57    ///
58    /// Use this together with `check_witness_uniform`.
59    fn grind_uniform(&mut self, bits: usize) -> Self::Witness;
60
61    /// Grinds based on *uniformly sampled bits*. This variant errors if a value is
62    /// sampled, which would violate our uniformity requirement (chance of about 1/P).
63    /// See the `UniformSamplingField` trait implemented for each field for details.
64    ///
65    /// Use this together with `check_witness_uniform_may_error`.
66    fn grind_uniform_may_error(&mut self, bits: usize) -> Self::Witness;
67
68    /// Check whether a given `witness` satisfies the PoW condition.
69    ///
70    /// After absorbing the witness, the challenger samples `bits` random bits
71    /// *uniformly* and verifies that all bits sampled are zero. The uniform
72    /// sampling implies we do rejection sampling in about ~1/P cases.
73    ///
74    /// Returns `true` if the witness passes the PoW check, `false` otherwise.
75    fn check_witness_uniform(&mut self, bits: usize, witness: Self::Witness) -> bool {
76        self.observe(witness);
77        self.sample_uniform_bits::<true>(bits)
78            .expect("Error impossible here due to resampling strategy")
79            == 0
80    }
81
82    /// Check whether a given `witness` satisfies the PoW condition.
83    ///
84    /// After absorbing the witness, the challenger samples `bits` random bits
85    /// *uniformly* and verifies that all bits sampled are zero. In about ~1/P
86    /// cases this function may error if a sampled value lies outside a range
87    /// in which we can guarantee uniform bits.
88    ///
89    /// Returns `true` if the witness passes the PoW check, `false` otherwise.
90    fn check_witness_uniform_may_error(&mut self, bits: usize, witness: Self::Witness) -> bool {
91        self.observe(witness);
92        self.sample_uniform_bits::<false>(bits)
93            .is_ok_and(|v| v == 0)
94    }
95}
96
97impl<F, P, const WIDTH: usize, const RATE: usize> GrindingChallenger
98    for DuplexChallenger<F, P, WIDTH, RATE>
99where
100    F: PrimeField64,
101    P: CryptographicPermutation<[F; WIDTH]>,
102{
103    type Witness = F;
104
105    #[instrument(name = "grind for proof-of-work witness", skip_all)]
106    fn grind(&mut self, bits: usize) -> Self::Witness {
107        assert!(bits < (usize::BITS as usize), "bit count must be valid");
108        assert!((1 << bits) < F::ORDER_U64);
109
110        let witness = (0..F::ORDER_U64)
111            .into_par_iter()
112            .map(|i| unsafe {
113                // i < F::ORDER_U64 by construction so this is safe.
114                F::from_canonical_unchecked(i)
115            })
116            .find_any(|witness| self.clone().check_witness(bits, *witness))
117            .expect("failed to find witness");
118        assert!(self.check_witness(bits, witness));
119        witness
120    }
121}
122
123impl<F, P, const WIDTH: usize, const RATE: usize> UniformGrindingChallenger
124    for DuplexChallenger<F, P, WIDTH, RATE>
125where
126    F: UniformSamplingField + PrimeField64,
127    P: CryptographicPermutation<[F; WIDTH]>,
128{
129    #[instrument(name = "grind uniform for proof-of-work witness", skip_all)]
130    fn grind_uniform(&mut self, bits: usize) -> Self::Witness {
131        // Call the generic grinder with the "resample" checking logic.
132        self.grind_generic(bits, |challenger, witness| {
133            challenger.check_witness_uniform(bits, witness)
134        })
135    }
136    #[instrument(name = "grind uniform may error for proof-of-work witness", skip_all)]
137    fn grind_uniform_may_error(&mut self, bits: usize) -> Self::Witness {
138        // Call the generic grinder with the "error" checking logic.
139        self.grind_generic(bits, |challenger, witness| {
140            challenger.check_witness_uniform_may_error(bits, witness)
141        })
142    }
143}
144impl<F, P, const WIDTH: usize, const RATE: usize> DuplexChallenger<F, P, WIDTH, RATE>
145where
146    F: PrimeField64,
147    P: CryptographicPermutation<[F; WIDTH]>,
148{
149    /// A generic, private helper for PoW grinding, parameterized by the checking function.
150    fn grind_generic<CHECK>(&mut self, bits: usize, check_fn: CHECK) -> F
151    where
152        CHECK: Fn(&mut Self, F) -> bool + Sync + Send,
153    {
154        // Maybe check that bits is greater than 0?
155        assert!(bits < (usize::BITS as usize), "bit count must be valid");
156        assert!(
157            (1u64 << bits) < F::ORDER_U64,
158            "bit count exceeds field order"
159        );
160        // The core parallel brute-force search logic.
161        let witness = (0..F::ORDER_U64)
162            .into_par_iter()
163            .map(|i| unsafe {
164                // This is safe as i is always in range.
165                F::from_canonical_unchecked(i)
166            })
167            .find_any(|&witness| check_fn(&mut self.clone(), witness))
168            .expect("failed to find proof-of-work witness");
169        // Run the check one last time on the *original* challenger to update its state
170        // and confirm the witness is valid.
171        assert!(check_fn(self, witness));
172        witness
173    }
174}
175
176impl<F, PF, P, const WIDTH: usize, const RATE: usize> GrindingChallenger
177    for MultiField32Challenger<F, PF, P, WIDTH, RATE>
178where
179    F: PrimeField32,
180    PF: PrimeField,
181    P: CryptographicPermutation<[PF; WIDTH]>,
182{
183    type Witness = F;
184
185    #[instrument(name = "grind for proof-of-work witness", skip_all)]
186    fn grind(&mut self, bits: usize) -> Self::Witness {
187        assert!(bits < (usize::BITS as usize), "bit count must be valid");
188        assert!((1 << bits) < F::ORDER_U32);
189        let witness = (0..F::ORDER_U32)
190            .into_par_iter()
191            .map(|i| unsafe {
192                // i < F::ORDER_U32 by construction so this is safe.
193                F::from_canonical_unchecked(i)
194            })
195            .find_any(|witness| self.clone().check_witness(bits, *witness))
196            .expect("failed to find witness");
197        assert!(self.check_witness(bits, witness));
198        witness
199    }
200}