Skip to content

Commit 423605f

Browse files
authored
Featurize middleware types that are actually defined by the backend (#94)
At the middleware we were defining some types that actually are dependant on the backend no matter how we define them in the middleware. For example, we were hardcoding the `Hash` and `Value` types and their related behaviour (eg. `.to_fields()`) to be based on the length of 4 field elements, but that's not a choice of the middleware, and in fact this is determined by the backend itself. On the same time, those types and related methods do not belong to the backend, since conceptually they are part of the middleware reasoning. The intention of this PR is not to prematurely abstract the library, but to avoid inconsistencies where a type or parameter is defined in the middleware to have certain carachteristic and later in the backend it gets used differently. The idea is that those types and parameters (eg. lengths) have a single source of truth in the code; and in the case of the "base types" (hash, value, etc) this is determined by the backend being used under the hood, not by a choice of the middleware parameters. The idea with this approach, is that the frontend & middleware should not need to import the proving library used by the backend (eg. plonky2, plonky3, etc). As mentioned earlier, the `Hash` and `Value` types are types belonging at the middleware, and is the middleware who reasons about them, but depending on the backend being used, the `Hash` and `Value` types will have different sizes. So it's the backend being used who actually defines their nature under the hood. For example with a plonky2 backend, these types will have a length of 4 field elements, whereas with a plonky3 backend they will have a length of 8 field eleements. Note that his approach does not introduce new traits or abstract code, just makes use of rust features to define 'base types' that are being used in the middleware.
1 parent af46ab7 commit 423605f

File tree

18 files changed

+359
-278
lines changed

18 files changed

+359
-278
lines changed

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ name = "pod2"
88
path = "src/lib.rs"
99

1010
[dependencies]
11-
plonky2 = { git = "https://github.com/0xPolygonZero/plonky2" }
1211
hex = "0.4.3"
1312
itertools = "0.14.0"
1413
strum = "0.26"
1514
strum_macros = "0.26"
1615
anyhow = "1.0.56"
1716
dyn-clone = "1.0.18"
17+
# enabled by features:
18+
plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true }
19+
20+
[features]
21+
default = ["backend_plonky2"]
22+
backend_plonky2 = ["plonky2"]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Usage
44
- Run tests: `cargo test --release`
5+
- Rustfmt: `cargo fmt`
6+
- Check [typos](https://github.com/crate-ci/typos): `typos -c .github/workflows/typos.toml`
57

68
## Book
79
The `book` contains the specification of POD2. A rendered version of the site can be found at: https://0xparc.github.io/pod2/

src/backends/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
pub mod mock_main;
2-
pub mod mock_signed;
1+
#[cfg(feature = "backend_plonky2")]
32
pub mod plonky2;

src/backends/plonky2.rs

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/backends/plonky2/basetypes.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//! This file exposes the middleware::basetypes to be used in the middleware when the
2+
//! `backend_plonky2` feature is enabled.
3+
//! See src/middleware/basetypes.rs for more details.
4+
5+
use anyhow::{anyhow, Error, Result};
6+
use hex::{FromHex, FromHexError};
7+
use plonky2::field::goldilocks_field::GoldilocksField;
8+
use plonky2::field::types::{Field, PrimeField64};
9+
use plonky2::hash::poseidon::PoseidonHash;
10+
use plonky2::plonk::config::Hasher;
11+
use plonky2::plonk::config::PoseidonGoldilocksConfig;
12+
use std::cmp::{Ord, Ordering};
13+
use std::fmt;
14+
15+
use crate::middleware::{Params, ToFields};
16+
17+
/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2
18+
pub type F = GoldilocksField;
19+
/// C is the Plonky2 config used in POD2 to work with Plonky2 recursion.
20+
pub type C = PoseidonGoldilocksConfig;
21+
/// D defines the extension degree of the field used in the Plonky2 proofs (quadratic extension).
22+
pub const D: usize = 2;
23+
24+
pub const HASH_SIZE: usize = 4;
25+
pub const VALUE_SIZE: usize = 4;
26+
27+
pub const EMPTY: Value = Value([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
28+
pub const SELF_ID_HASH: Hash = Hash([F::ONE, F::ZERO, F::ZERO, F::ZERO]);
29+
pub const NULL: Hash = Hash([F::ZERO, F::ZERO, F::ZERO, F::ZERO]);
30+
31+
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
32+
pub struct Value(pub [F; VALUE_SIZE]);
33+
34+
impl ToFields for Value {
35+
fn to_fields(&self, _params: &Params) -> (Vec<F>, usize) {
36+
(self.0.to_vec(), VALUE_SIZE)
37+
}
38+
}
39+
40+
impl Value {
41+
pub fn to_bytes(self) -> Vec<u8> {
42+
self.0
43+
.iter()
44+
.flat_map(|e| e.to_canonical_u64().to_le_bytes())
45+
.collect()
46+
}
47+
}
48+
49+
impl Ord for Value {
50+
fn cmp(&self, other: &Self) -> Ordering {
51+
for (lhs, rhs) in self.0.iter().zip(other.0.iter()).rev() {
52+
let (lhs, rhs) = (lhs.to_canonical_u64(), rhs.to_canonical_u64());
53+
if lhs < rhs {
54+
return Ordering::Less;
55+
} else if lhs > rhs {
56+
return Ordering::Greater;
57+
}
58+
}
59+
return Ordering::Equal;
60+
}
61+
}
62+
63+
impl PartialOrd for Value {
64+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
65+
Some(self.cmp(other))
66+
}
67+
}
68+
69+
impl From<i64> for Value {
70+
fn from(v: i64) -> Self {
71+
let lo = F::from_canonical_u64((v as u64) & 0xffffffff);
72+
let hi = F::from_canonical_u64((v as u64) >> 32);
73+
Value([lo, hi, F::ZERO, F::ZERO])
74+
}
75+
}
76+
77+
impl From<Hash> for Value {
78+
fn from(h: Hash) -> Self {
79+
Value(h.0)
80+
}
81+
}
82+
83+
impl TryInto<i64> for Value {
84+
type Error = Error;
85+
fn try_into(self) -> std::result::Result<i64, Self::Error> {
86+
let value = self.0;
87+
if &value[2..] != &[F::ZERO, F::ZERO]
88+
|| value[..2]
89+
.iter()
90+
.all(|x| x.to_canonical_u64() > u32::MAX as u64)
91+
{
92+
Err(anyhow!("Value not an element of the i64 embedding."))
93+
} else {
94+
Ok((value[0].to_canonical_u64() + value[1].to_canonical_u64() << 32) as i64)
95+
}
96+
}
97+
}
98+
99+
impl fmt::Display for Value {
100+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101+
if self.0[2].is_zero() && self.0[3].is_zero() {
102+
// Assume this is an integer
103+
let (l0, l1) = (self.0[0].to_canonical_u64(), self.0[1].to_canonical_u64());
104+
assert!(l0 < (1 << 32));
105+
assert!(l1 < (1 << 32));
106+
write!(f, "{}", l0 + l1 * (1 << 32))
107+
} else {
108+
// Assume this is a hash
109+
Hash(self.0).fmt(f)
110+
}
111+
}
112+
}
113+
114+
#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)]
115+
pub struct Hash(pub [F; HASH_SIZE]);
116+
117+
pub fn hash_value(input: &Value) -> Hash {
118+
Hash(PoseidonHash::hash_no_pad(&input.0).elements)
119+
}
120+
pub fn hash_fields(input: &[F]) -> Hash {
121+
Hash(PoseidonHash::hash_no_pad(&input).elements)
122+
}
123+
124+
impl From<Value> for Hash {
125+
fn from(v: Value) -> Self {
126+
Hash(v.0)
127+
}
128+
}
129+
impl Hash {
130+
pub fn value(self) -> Value {
131+
Value(self.0)
132+
}
133+
}
134+
135+
impl ToFields for Hash {
136+
fn to_fields(&self, _params: &Params) -> (Vec<F>, usize) {
137+
(self.0.to_vec(), VALUE_SIZE)
138+
}
139+
}
140+
141+
impl Ord for Hash {
142+
fn cmp(&self, other: &Self) -> Ordering {
143+
Value(self.0).cmp(&Value(other.0))
144+
}
145+
}
146+
147+
impl PartialOrd for Hash {
148+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
149+
Some(self.cmp(other))
150+
}
151+
}
152+
153+
impl fmt::Display for Hash {
154+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155+
let v0 = self.0[0].to_canonical_u64();
156+
for i in 0..4 {
157+
write!(f, "{:02x}", (v0 >> (i * 8)) & 0xff)?;
158+
}
159+
write!(f, "…")
160+
}
161+
}
162+
163+
impl FromHex for Hash {
164+
type Error = FromHexError;
165+
166+
// TODO make it dependant on backend::Value len
167+
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
168+
// In little endian
169+
let bytes = <[u8; 32]>::from_hex(hex)?;
170+
let mut buf: [u8; 8] = [0; 8];
171+
let mut inner = [F::ZERO; 4];
172+
for i in 0..4 {
173+
buf.copy_from_slice(&bytes[8 * i..8 * (i + 1)]);
174+
inner[i] = F::from_canonical_u64(u64::from_le_bytes(buf));
175+
}
176+
Ok(Self(inner))
177+
}
178+
}
179+
180+
impl From<&str> for Hash {
181+
fn from(s: &str) -> Self {
182+
hash_str(s)
183+
}
184+
}
185+
186+
pub fn hash_str(s: &str) -> Hash {
187+
let mut input = s.as_bytes().to_vec();
188+
input.push(1); // padding
189+
190+
// Merge 7 bytes into 1 field, because the field is slightly below 64 bits
191+
let input: Vec<F> = input
192+
.chunks(7)
193+
.map(|bytes| {
194+
let mut v: u64 = 0;
195+
for b in bytes.iter().rev() {
196+
v <<= 8;
197+
v += *b as u64;
198+
}
199+
F::from_canonical_u64(v)
200+
})
201+
.collect();
202+
Hash(PoseidonHash::hash_no_pad(&input).elements)
203+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ impl MockMainPod {
327327
statements[statements.len() - params.max_public_statements..].to_vec();
328328

329329
// get the id out of the public statements
330-
let id: PodId = PodId(hash_statements(&public_statements, *params));
330+
let id: PodId = PodId(hash_statements(&public_statements, params));
331331

332332
Ok(Self {
333333
params: params.clone(),
@@ -362,10 +362,10 @@ impl MockMainPod {
362362
}
363363
}
364364

365-
pub fn hash_statements(statements: &[Statement], params: Params) -> middleware::Hash {
365+
pub fn hash_statements(statements: &[Statement], _params: &Params) -> middleware::Hash {
366366
let field_elems = statements
367367
.into_iter()
368-
.flat_map(|statement| statement.clone().to_fields(params).0)
368+
.flat_map(|statement| statement.clone().to_fields(_params).0)
369369
.collect::<Vec<_>>();
370370
Hash(PoseidonHash::hash_no_pad(&field_elems).elements)
371371
}
@@ -376,7 +376,7 @@ impl Pod for MockMainPod {
376376
// get the input_statements from the self.statements
377377
let input_statements = &self.statements[input_statement_offset..];
378378
// get the id out of the public statements, and ensure it is equal to self.id
379-
let ids_match = self.id == PodId(hash_statements(&self.public_statements, self.params));
379+
let ids_match = self.id == PodId(hash_statements(&self.public_statements, &self.params));
380380
// find a ValueOf statement from the public statements with key=KEY_TYPE and check that the
381381
// value is PodType::MockMainPod
382382
let has_type_statement = self
@@ -474,7 +474,7 @@ impl Pod for MockMainPod {
474474
#[cfg(test)]
475475
pub mod tests {
476476
use super::*;
477-
use crate::backends::mock_signed::MockSigner;
477+
use crate::backends::plonky2::mock_signed::MockSigner;
478478
use crate::examples::{
479479
great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder,
480480
zu_kyc_sign_pod_builders,
File renamed without changes.

src/backends/mock_main/statement.rs renamed to src/backends/plonky2/mock_main/statement.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ impl Statement {
2121
}
2222

2323
impl ToFields for Statement {
24-
fn to_fields(&self, params: Params) -> (Vec<middleware::F>, usize) {
25-
let (native_statement_f, native_statement_f_len) = self.0.to_fields(params);
24+
fn to_fields(&self, _params: &Params) -> (Vec<middleware::F>, usize) {
25+
let (native_statement_f, native_statement_f_len) = self.0.to_fields(_params);
2626
let (vec_statementarg_f, vec_statementarg_f_len) = self
2727
.1
2828
.clone()
2929
.into_iter()
30-
.map(|statement_arg| statement_arg.to_fields(params))
30+
.map(|statement_arg| statement_arg.to_fields(_params))
3131
.fold((Vec::new(), 0), |mut acc, (f, l)| {
3232
acc.0.extend(f);
3333
acc.1 += l;

src/backends/plonky2/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod basetypes;
2+
pub mod mock_main;
3+
pub mod mock_signed;

0 commit comments

Comments
 (0)