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}