openvm_continuations/
commit_bytes.rs1use std::{array::from_fn, fmt};
2
3use num_bigint::BigUint;
4use openvm_stark_backend::codec::{Decode, Encode};
5use openvm_stark_sdk::config::baby_bear_poseidon2::{DIGEST_SIZE, F};
6use openvm_verify_stark_host::pvs::VkCommit;
7use p3_field::{PrimeCharacteristicRing, PrimeField32};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10pub const COMMIT_NUM_BYTES: usize = 32;
11
12#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode)]
17pub struct CommitBytes([u8; COMMIT_NUM_BYTES]);
18
19#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
20pub enum CommitBytesError {
21 #[error("non-canonical CommitBytes for BabyBear digest")]
22 NonCanonical,
23}
24
25impl CommitBytes {
26 pub fn try_new(bytes: [u8; COMMIT_NUM_BYTES]) -> Result<Self, CommitBytesError> {
28 if u32_digest_to_bytes(&bytes_to_u32_digest(&bytes)) == bytes {
29 Ok(Self(bytes))
30 } else {
31 Err(CommitBytesError::NonCanonical)
32 }
33 }
34
35 pub fn new(bytes: [u8; COMMIT_NUM_BYTES]) -> Self {
37 Self::try_new(bytes).expect("non-canonical CommitBytes for BabyBear digest")
38 }
39
40 pub fn as_slice(&self) -> &[u8; COMMIT_NUM_BYTES] {
41 &self.0
42 }
43
44 pub fn to_field_le_bytes(self) -> [u8; COMMIT_NUM_BYTES] {
45 let digest: [F; DIGEST_SIZE] = self.into();
46 let mut bytes = [0u8; COMMIT_NUM_BYTES];
47 for (i, limb) in digest.into_iter().enumerate() {
48 bytes[4 * i..4 * (i + 1)].copy_from_slice(&limb.to_unique_u32().to_le_bytes());
49 }
50 bytes
51 }
52}
53
54#[derive(Copy, Clone, Debug, PartialEq, Eq)]
55pub struct VkCommitBytes {
56 pub cached_commit: CommitBytes,
57 pub vk_pre_hash: CommitBytes,
58}
59
60impl<F: PrimeCharacteristicRing> From<VkCommitBytes> for VkCommit<F> {
61 fn from(value: VkCommitBytes) -> Self {
62 VkCommit {
63 cached_commit: value.cached_commit.into(),
64 vk_pre_hash: value.vk_pre_hash.into(),
65 }
66 }
67}
68
69impl From<[F; DIGEST_SIZE]> for CommitBytes {
70 fn from(value: [F; DIGEST_SIZE]) -> Self {
71 Self::from(value.map(|x| x.as_canonical_u32()))
72 }
73}
74
75impl From<[u32; DIGEST_SIZE]> for CommitBytes {
76 fn from(value: [u32; DIGEST_SIZE]) -> Self {
77 assert!(
78 value.iter().all(|&digit| digit < F::ORDER_U32),
79 "non-canonical BabyBear digest limb"
80 );
81 Self(u32_digest_to_bytes(&value))
82 }
83}
84
85impl<F: PrimeCharacteristicRing> From<CommitBytes> for [F; DIGEST_SIZE] {
86 fn from(value: CommitBytes) -> Self {
87 assert!(
88 u32_digest_to_bytes(&bytes_to_u32_digest(&value.0)) == value.0,
89 "non-canonical CommitBytes for BabyBear digest"
90 );
91 bytes_to_u32_digest(&value.0).map(F::from_u32)
92 }
93}
94
95fn bytes_to_biguint(bytes: &[u8; COMMIT_NUM_BYTES]) -> BigUint {
96 let mut bigint = BigUint::ZERO;
97 for byte in bytes.iter() {
98 bigint <<= 8;
99 bigint += BigUint::from(*byte);
100 }
101 bigint
102}
103
104fn biguint_to_u32_digest(mut bigint: BigUint) -> [u32; DIGEST_SIZE] {
105 let order = F::ORDER_U32;
106 from_fn(|_| {
107 let bigint_digit = bigint.clone() % order;
108 let digit = if bigint_digit == BigUint::ZERO {
109 0u32
110 } else {
111 bigint_digit.to_u32_digits()[0]
112 };
113 bigint /= order;
114 digit
115 })
116}
117
118fn u32_digest_to_biguint(digest: &[u32; DIGEST_SIZE]) -> BigUint {
119 let mut bigint = BigUint::ZERO;
120 let mut base = BigUint::from(1u32);
121 let order = BigUint::from(F::ORDER_U32);
122 for digit in digest {
123 bigint += &base * BigUint::from(*digit);
124 base *= ℴ
125 }
126 bigint
127}
128
129fn bytes_to_u32_digest(bytes: &[u8; COMMIT_NUM_BYTES]) -> [u32; DIGEST_SIZE] {
130 biguint_to_u32_digest(bytes_to_biguint(bytes))
131}
132
133fn u32_digest_to_bytes(digest: &[u32; DIGEST_SIZE]) -> [u8; COMMIT_NUM_BYTES] {
134 let mut ret = [0u8; COMMIT_NUM_BYTES];
135 let bytes = u32_digest_to_biguint(digest).to_bytes_be();
136 let start = COMMIT_NUM_BYTES - bytes.len();
137 ret[start..].copy_from_slice(&bytes);
138 ret
139}
140
141impl fmt::Display for CommitBytes {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "0x{}", hex::encode(self.as_slice()))
144 }
145}
146
147impl Serialize for CommitBytes {
148 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
149 self.to_string().serialize(serializer)
150 }
151}
152
153impl<'de> Deserialize<'de> for CommitBytes {
154 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
155 let hex_str = String::deserialize(deserializer)?;
156 let hex_str = hex_str.strip_prefix("0x").unwrap_or(&hex_str);
157 let bytes: [u8; COMMIT_NUM_BYTES] = hex::decode(hex_str)
158 .map_err(serde::de::Error::custom)?
159 .try_into()
160 .map_err(|_| serde::de::Error::custom("expected 32 bytes"))?;
161 CommitBytes::try_new(bytes).map_err(serde::de::Error::custom)
162 }
163}
164
165impl Decode for CommitBytes {
167 fn decode<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
168 let bytes = <[u8; COMMIT_NUM_BYTES]>::decode(reader)?;
169 CommitBytes::try_new(bytes).map_err(std::io::Error::other)
170 }
171}
172
173#[cfg(feature = "root-prover")]
174mod bn254 {
175 use p3_bn254::Bn254;
176 use p3_field::PrimeField;
177
178 use super::*;
179
180 impl From<Bn254> for CommitBytes {
181 fn from(value: Bn254) -> Self {
182 Self::new(bn254_to_bytes(value))
183 }
184 }
185
186 impl From<[Bn254; 1]> for CommitBytes {
187 fn from(value: [Bn254; 1]) -> Self {
188 CommitBytes::from(value[0])
189 }
190 }
191
192 impl From<CommitBytes> for Bn254 {
193 fn from(value: CommitBytes) -> Self {
194 bytes_to_bn254(&value.0)
195 }
196 }
197
198 fn bytes_to_bn254(bytes: &[u8; COMMIT_NUM_BYTES]) -> Bn254 {
199 let order = Bn254::from_u32(1 << 8);
200 let mut ret = Bn254::ZERO;
201 let mut base = Bn254::ONE;
202 for byte in bytes.iter().rev() {
203 ret += base * Bn254::from_u8(*byte);
204 base *= order;
205 }
206 ret
207 }
208
209 fn bn254_to_bytes(bn254: Bn254) -> [u8; COMMIT_NUM_BYTES] {
210 let mut ret = [0u8; COMMIT_NUM_BYTES];
211 let bytes = bn254.as_canonical_biguint().to_bytes_be();
212 let start = COMMIT_NUM_BYTES - bytes.len();
213 ret[start..].copy_from_slice(&bytes);
214 ret
215 }
216}
217
218#[cfg(test)]
220mod tests {
221 use serde::de::IntoDeserializer;
222
223 use super::*;
224
225 fn max_canonical() -> [u8; COMMIT_NUM_BYTES] {
227 u32_digest_to_bytes(&[F::ORDER_U32 - 1; DIGEST_SIZE])
228 }
229
230 #[test]
231 fn try_new_canonicity() {
232 assert!(CommitBytes::try_new([0u8; COMMIT_NUM_BYTES]).is_ok());
233 assert!(CommitBytes::try_new(max_canonical()).is_ok());
234 assert_eq!(
235 CommitBytes::try_new([0xff; COMMIT_NUM_BYTES]),
236 Err(CommitBytesError::NonCanonical)
237 );
238 }
239
240 #[test]
241 fn deserialize_rejects_non_canonical() {
242 let hex = format!("0x{}", hex::encode([0xffu8; COMMIT_NUM_BYTES]));
243 let de: serde::de::value::StrDeserializer<serde::de::value::Error> =
244 hex.as_str().into_deserializer();
245 assert!(CommitBytes::deserialize(de).is_err());
246 }
247
248 #[test]
249 fn decode_rejects_non_canonical() {
250 let encoded = [0xffu8; COMMIT_NUM_BYTES];
251 assert!(CommitBytes::decode(&mut encoded.as_slice()).is_err());
252 }
253
254 #[test]
255 fn encode_decode_roundtrip() {
256 let commit = CommitBytes::try_new(max_canonical()).unwrap();
257 let bytes = commit.encode_to_vec().unwrap();
258 assert_eq!(CommitBytes::decode_from_bytes(&bytes).unwrap(), commit);
259 }
260}