Skip to main content

p3_challenger/fs/
transcript_field.rs

1//! Field representation hooks for typed, native-field transcripts.
2
3use alloc::vec::Vec;
4
5use p3_field::{Field, PrimeField64};
6
7use super::codecs::{decode_field_be_canonical, encode_field_be, field_byte_size};
8use super::{FieldUnit, TranscriptError, TypeTag};
9use crate::CanObserve;
10
11/// A field with stable encodings for a typed native-field transcript.
12///
13/// Implementations must make seed absorption injective and use a unique, fixed-width
14/// wire encoding for every element. The algebra tag must identify the coefficient
15/// field and its representation independently of Rust type names, and bind the
16/// supplied extension degree and basis digest unchanged. These contracts are required
17/// for transcript binding.
18///
19/// Prime fields below `2^64` retain their canonical big-endian wire format and
20/// length-prefixed little-endian seed packing through the blanket implementation.
21pub trait TranscriptField: Field {
22    /// Stable identity of an algebra with `degree` coefficients over this field.
23    /// `basis` is Keccak-256 of its [`p3_field::AlgebraIdentity`] bytes and must be
24    /// included unchanged in the returned tag.
25    fn algebra_tag(degree: usize, basis: [u8; 32]) -> TypeTag;
26
27    /// Absorb a byte string injectively into this field's sponge alphabet.
28    fn observe_seed<C: CanObserve<Self>>(challenger: &mut C, bytes: &[u8]);
29
30    /// Byte length of the canonical encoding of one element.
31    fn wire_len() -> usize;
32
33    /// Append exactly `Self::wire_len()` bytes identifying this element uniquely.
34    fn encode(value: &Self, out: &mut Vec<u8>);
35
36    /// Decode the first `Self::wire_len()` bytes, rejecting short or noncanonical inputs.
37    fn decode(bytes: &[u8]) -> Result<Self, TranscriptError>;
38}
39
40impl<F: PrimeField64> TranscriptField for F {
41    fn algebra_tag(degree: usize, basis: [u8; 32]) -> TypeTag {
42        TypeTag::Algebra {
43            modulus: F::ORDER_U64,
44            degree,
45            basis,
46        }
47    }
48
49    fn observe_seed<C: CanObserve<F>>(challenger: &mut C, bytes: &[u8]) {
50        let chunk = FieldUnit::<F>::bytes_per_element();
51        // Length below the modulus keeps the length element itself injective.
52        assert!(
53            (bytes.len() as u128) < F::ORDER_U64 as u128,
54            "byte string of {} bytes does not fit in one field element",
55            bytes.len(),
56        );
57        // One element for the length, then one per chunk.
58        let mut packed: Vec<F> = Vec::with_capacity(1 + bytes.len().div_ceil(chunk));
59        packed.push(F::from_u64(bytes.len() as u64));
60        for window in bytes.chunks(chunk) {
61            // Little-endian fold of at most `chunk` bytes: value < 2^(8*chunk) < p.
62            let mut acc = 0u64;
63            for (i, &b) in window.iter().enumerate() {
64                acc |= (b as u64) << (8 * i);
65            }
66            packed.push(F::from_u64(acc));
67        }
68        challenger.observe_slice(&packed);
69    }
70
71    fn wire_len() -> usize {
72        field_byte_size::<F>()
73    }
74
75    fn encode(value: &Self, out: &mut Vec<u8>) {
76        encode_field_be(value, out);
77    }
78
79    fn decode(bytes: &[u8]) -> Result<Self, TranscriptError> {
80        decode_field_be_canonical(bytes)
81    }
82}