Skip to content

Commit 5cefa2a

Browse files
committed
feat: playing around with supernova rehashing
1 parent fbf51c8 commit 5cefa2a

File tree

5 files changed

+237
-9
lines changed

5 files changed

+237
-9
lines changed

Cargo.lock

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ keystore-rs = "0.1.0"
5252
toml = "0.8.14"
5353
dirs = "5.0.1"
5454
anyhow = "1.0.44"
55-
jmt = { git = "https://github.com/deltadevsde/jmt", features = [
55+
jmt = { path = "../jmt", features = [
5656
"mocks",
5757
] } #{ version = "0.10.0", features = ["mocks"] }
5858
bellpepper-core = { version = "0.4.0", default-features = false }
59+
bellpepper = "0.4.1"
5960
arecibo = { git = "https://github.com/deltadevsde/arecibo" }
6061
itertools = "0.13.0" # zip_eq
6162
sha2 = "0.10.8"

src/common.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::{bail, Result};
22
use borsh::{BorshDeserialize, BorshSerialize};
3+
use ff::derive::bitvec::view::AsBits;
34
use jmt::KeyHash;
45
use serde::{Deserialize, Serialize};
56
use std::{
@@ -134,6 +135,11 @@ impl Hashchain {
134135
self.push(operation)
135136
}
136137

138+
pub fn to_bytes(&self) -> Vec<u8> {
139+
self.last()
140+
.map_or(Vec::new(), |last_entry| last_entry.to_bytes())
141+
}
142+
137143
pub fn get(&self, idx: usize) -> &HashchainEntry {
138144
&self.entries[idx]
139145
}
@@ -207,4 +213,14 @@ impl HashchainEntry {
207213
operation,
208214
}
209215
}
216+
217+
pub fn to_bytes(&self) -> Vec<u8> {
218+
let mut bytes = Vec::new();
219+
220+
bytes.extend_from_slice(self.hash.as_ref());
221+
bytes.extend_from_slice(self.previous_hash.as_ref());
222+
bytes.extend_from_slice(self.operation.to_string().as_bytes());
223+
224+
bytes
225+
}
210226
}

src/nova/insert.rs

Lines changed: 204 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,224 @@
11
use crate::{
22
nova::utils::{next_rom_index_and_pc, Digest},
3-
tree::InsertProof,
3+
tree::{Hasher, InsertProof, NonMembershipProof},
44
};
55
use anyhow::Result;
66
use arecibo::supernova::StepCircuit;
7-
use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError};
8-
use ff::PrimeField;
7+
use bellpepper::gadgets::sha256::sha256;
8+
use bellpepper_core::{
9+
boolean::{AllocatedBit, Boolean},
10+
num::AllocatedNum,
11+
ConstraintSystem, SynthesisError,
12+
};
13+
use ff::{PrimeField, PrimeFieldBits};
14+
use jmt::proof::UpdateMerkleProof;
15+
use std::marker::PhantomData;
16+
17+
#[derive(Clone)]
18+
struct InsertProofCircuit<Scalar: PrimeField> {
19+
proof: InsertProof,
20+
_p: PhantomData<Scalar>,
21+
}
22+
23+
impl<Scalar: PrimeField + PrimeFieldBits> InsertProofCircuit<Scalar> {
24+
pub fn new(proof: InsertProof) -> Self {
25+
Self {
26+
proof,
27+
_p: PhantomData,
28+
}
29+
}
30+
}
31+
32+
impl<Scalar: PrimeField + PrimeFieldBits> StepCircuit<Scalar> for InsertProofCircuit<Scalar> {
33+
fn arity(&self) -> usize {
34+
1
35+
}
36+
37+
fn synthesize<CS: ConstraintSystem<Scalar>>(
38+
&self,
39+
cs: &mut CS,
40+
pc: Option<&AllocatedNum<Scalar>>,
41+
z: &[AllocatedNum<Scalar>],
42+
) -> Result<(Option<AllocatedNum<Scalar>>, Vec<AllocatedNum<Scalar>>), SynthesisError> {
43+
let mut z_out: Vec<AllocatedNum<Scalar>> = Vec::new();
44+
45+
// Allocate the old root
46+
let old_root = AllocatedNum::alloc(cs.namespace(|| "old_root"), || {
47+
Ok(Digest::new(self.proof.non_membership_proof.root)
48+
.to_scalar()
49+
.map_err(|_| SynthesisError::Unsatisfiable)?)
50+
})?;
51+
52+
// Allocate the new root
53+
let new_root = AllocatedNum::alloc(cs.namespace(|| "new_root"), || {
54+
Ok(Digest::new(self.proof.new_root)
55+
.to_scalar()
56+
.map_err(|_| SynthesisError::Unsatisfiable)?)
57+
})?;
58+
59+
// Allocate the key
60+
let key_bits = allocate_bits_to_binary_number(
61+
cs.namespace(|| "key"),
62+
Some(self.proof.non_membership_proof.key.0.to_vec()),
63+
)?;
64+
65+
// Allocate the value
66+
let value_bytes = self.proof.value.to_bytes();
67+
let mut value_bits = Vec::new();
68+
69+
for (byte_idx, &byte) in value_bytes.iter().enumerate() {
70+
for bit_idx in 0..8 {
71+
let bit = AllocatedBit::alloc(
72+
cs.namespace(|| format!("value bit {}.{}", byte_idx, bit_idx)),
73+
Some((byte >> bit_idx) & 1 == 1),
74+
)?;
75+
value_bits.push(Boolean::from(bit));
76+
}
77+
}
78+
79+
// Hash the key and value
80+
let leaf_hash = sha256(
81+
cs.namespace(|| "leaf_hash"),
82+
&[key_bits.clone(), value_bits].concat(),
83+
)
84+
.map_err(|e| SynthesisError::Unsatisfiable)?;
85+
86+
// Verify the non-membership proof
87+
verify_non_membership_proof(
88+
cs.namespace(|| "non_membership_proof"),
89+
&self.proof.non_membership_proof,
90+
&old_root,
91+
&key_bits,
92+
)?;
93+
94+
// Verify the membership proof (update)
95+
verify_membership_proof(
96+
cs.namespace(|| "membership_proof"),
97+
&self.proof.membership_proof,
98+
&old_root,
99+
&new_root,
100+
&key_bits,
101+
&leaf_hash,
102+
)?;
103+
104+
z_out.push(new_root);
105+
106+
let new_pc = match pc {
107+
Some(old_pc) => {
108+
let new_pc =
109+
AllocatedNum::alloc(cs.namespace(|| "new_pc"), || match old_pc.get_value() {
110+
Some(v) => Ok(v + Scalar::from(1)),
111+
None => Err(SynthesisError::AssignmentMissing),
112+
})?;
113+
114+
// Enforce that new_pc = old_pc + 1
115+
cs.enforce(
116+
|| "new_pc = old_pc + 1",
117+
|lc| lc + old_pc.get_variable(),
118+
|lc| lc + CS::one(),
119+
|lc| lc + new_pc.get_variable(),
120+
);
121+
122+
Some(new_pc)
123+
}
124+
None => None,
125+
};
126+
127+
Ok((new_pc, z_out))
128+
}
129+
130+
fn circuit_index(&self) -> usize {
131+
0
132+
}
133+
}
134+
135+
fn allocate_bits_to_binary_number<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
136+
mut cs: CS,
137+
value: Option<Vec<u8>>,
138+
) -> Result<Vec<Boolean>, SynthesisError> {
139+
let bits = value
140+
.map(|bytes| {
141+
bytes
142+
.iter()
143+
.flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1 == 1))
144+
.collect::<Vec<_>>()
145+
})
146+
.unwrap_or_else(|| vec![false; 256]);
147+
148+
let mut result = Vec::new();
149+
for (i, &bit) in bits.iter().enumerate() {
150+
let allocated_bit = AllocatedBit::alloc(cs.namespace(|| format!("bit {}", i)), Some(bit))?;
151+
result.push(Boolean::from(allocated_bit));
152+
}
153+
Ok(result)
154+
}
155+
156+
fn verify_non_membership_proof<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
157+
mut cs: CS,
158+
proof: &NonMembershipProof,
159+
root: &[Boolean],
160+
key: &[Boolean],
161+
) -> Result<(), SynthesisError> {
162+
// 1. Hash the key
163+
let key_hash = sha256(cs.namespace(|| "hash key"), key)?;
164+
165+
// 2. Traverse the Merkle path
166+
let mut current = key_hash;
167+
for (i, sibling) in proof.proof.siblings().iter().enumerate() {
168+
let sibling_bits = allocate_bits_to_binary_number(
169+
cs.namespace(|| format!("sibling bits {}", i)),
170+
Some(sibling.to_vec()),
171+
)?;
172+
173+
let (left, right) = if *is_left {
174+
(sibling_bits, current)
175+
} else {
176+
(current, sibling_bits)
177+
};
178+
179+
current = sha256(
180+
cs.namespace(|| format!("hash node {}", i)),
181+
&[left, right].concat(),
182+
)?;
183+
}
184+
185+
// 3. Check that the computed root does not match the given root
186+
for (i, (computed_bit, given_bit)) in current.iter().zip(root.iter()).enumerate() {
187+
Boolean::enforce_not_equal(
188+
cs.namespace(|| format!("root bit {} should not be equal", i)),
189+
computed_bit,
190+
given_bit,
191+
)?;
192+
}
193+
194+
Ok(())
195+
}
196+
197+
fn verify_membership_proof<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
198+
mut cs: CS,
199+
proof: &UpdateMerkleProof<Hasher>,
200+
old_root: &AllocatedNum<Scalar>,
201+
new_root: &AllocatedNum<Scalar>,
202+
key: &[Boolean],
203+
leaf_hash: &[Boolean],
204+
) -> Result<(), SynthesisError> {
205+
// lfg implementing the logic to verify the membership proof
206+
Ok(())
207+
}
9208

10209
#[derive(Clone)]
11210
pub struct InsertCircuit<F> {
12211
pub insertion_proof: InsertProof,
13212
rom_size: usize,
14-
_phantom: std::marker::PhantomData<F>,
213+
_phantom: PhantomData<F>,
15214
}
16215

17216
impl<F: PrimeField> InsertCircuit<F> {
18217
pub fn new(insertion_proof: InsertProof, rom_size: usize) -> Self {
19218
Self {
20219
insertion_proof,
21220
rom_size,
22-
_phantom: std::marker::PhantomData,
221+
_phantom: PhantomData,
23222
}
24223
}
25224
}

src/nova/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::tree::*;
44
use crate::{common::Hashchain, nova::batch::EpochCircuit};
55
use anyhow::Result;
66
use arecibo::{provider::PallasEngine, supernova::PublicParams, traits::snark::default_ck_hint};
7+
use bellpepper::gadgets::sha256::sha256;
78
use bellpepper_core::{
89
boolean::{AllocatedBit, Boolean},
910
num::AllocatedNum,

0 commit comments

Comments
 (0)