liminal_ark_relations/shielder/
deposit_and_merge.rs1use liminal_ark_relation_macro::snark_relation;
2
3#[snark_relation]
14mod relation {
15 #[cfg(feature = "circuit")]
16 use {
17 crate::shielder::{
18 check_merkle_proof, note_var::NoteVarBuilder, path_shape_var::PathShapeVar,
19 token_amount_var::TokenAmountVar,
20 },
21 ark_r1cs_std::{
22 alloc::{
23 AllocVar,
24 AllocationMode::{Input, Witness},
25 },
26 eq::EqGadget,
27 fields::fp::FpVar,
28 },
29 ark_relations::ns,
30 core::ops::Add,
31 };
32
33 use crate::shielder::{
34 convert_hash, convert_vec,
35 types::{
36 BackendLeafIndex, BackendMerklePath, BackendMerkleRoot, BackendNote, BackendNullifier,
37 BackendTokenAmount, BackendTokenId, BackendTrapdoor, FrontendLeafIndex,
38 FrontendMerklePath, FrontendMerkleRoot, FrontendNote, FrontendNullifier,
39 FrontendTokenAmount, FrontendTokenId, FrontendTrapdoor,
40 },
41 };
42
43 #[relation_object_definition]
44 #[derive(Clone, Debug)]
45 struct DepositAndMergeRelation {
46 #[constant]
47 pub max_path_len: u8,
48
49 #[public_input(frontend_type = "FrontendTokenId")]
51 pub token_id: BackendTokenId,
52 #[public_input(frontend_type = "FrontendNullifier", parse_with = "convert_hash")]
53 pub old_nullifier: BackendNullifier,
54 #[public_input(frontend_type = "FrontendNote", parse_with = "convert_hash")]
55 pub new_note: BackendNote,
56 #[public_input(frontend_type = "FrontendTokenAmount")]
57 pub token_amount: BackendTokenAmount,
58 #[public_input(frontend_type = "FrontendMerkleRoot", parse_with = "convert_hash")]
59 pub merkle_root: BackendMerkleRoot,
60
61 #[private_input(frontend_type = "FrontendTrapdoor", parse_with = "convert_hash")]
63 pub old_trapdoor: BackendTrapdoor,
64 #[private_input(frontend_type = "FrontendTrapdoor", parse_with = "convert_hash")]
65 pub new_trapdoor: BackendTrapdoor,
66 #[private_input(frontend_type = "FrontendNullifier", parse_with = "convert_hash")]
67 pub new_nullifier: BackendNullifier,
68 #[private_input(frontend_type = "FrontendMerklePath", parse_with = "convert_vec")]
69 pub merkle_path: BackendMerklePath,
70 #[private_input(frontend_type = "FrontendLeafIndex")]
71 pub leaf_index: BackendLeafIndex,
72 #[private_input(frontend_type = "FrontendNote", parse_with = "convert_hash")]
73 pub old_note: BackendNote,
74 #[private_input(frontend_type = "FrontendTokenAmount")]
75 pub old_token_amount: BackendTokenAmount,
76 #[private_input(frontend_type = "FrontendTokenAmount")]
77 pub new_token_amount: BackendTokenAmount,
78 }
79
80 #[cfg(feature = "circuit")]
81 #[circuit_definition]
82 fn generate_constraints() {
83 let old_note = NoteVarBuilder::new(cs.clone())
87 .with_token_id(self.token_id(), Input)?
88 .with_token_amount(self.old_token_amount(), Witness)?
89 .with_trapdoor(self.old_trapdoor(), Witness)?
90 .with_nullifier(self.old_nullifier(), Input)?
91 .with_note(self.old_note(), Witness)?
92 .build()?;
93
94 let new_note = NoteVarBuilder::new(cs.clone())
98 .with_token_id_var(old_note.token_id.clone())
99 .with_token_amount(self.new_token_amount(), Witness)?
100 .with_trapdoor(self.new_trapdoor(), Witness)?
101 .with_nullifier(self.new_nullifier(), Witness)?
102 .with_note(self.new_note(), Input)?
103 .build()?;
104
105 let token_amount =
109 TokenAmountVar::new_input(ns!(cs, "token amount"), || self.token_amount())?;
110 let token_sum = token_amount.add(old_note.token_amount)?;
111 token_sum.enforce_equal(&new_note.token_amount)?;
112
113 let merkle_root = FpVar::new_input(ns!(cs, "merkle root"), || self.merkle_root())?;
117 let path_shape = PathShapeVar::new_witness(ns!(cs, "path shape"), || {
118 Ok((*self.max_path_len(), self.leaf_index().cloned()))
119 })?;
120
121 check_merkle_proof(
122 merkle_root,
123 path_shape,
124 old_note.note,
125 self.merkle_path().cloned().unwrap_or_default(),
126 *self.max_path_len(),
127 cs,
128 )
129 }
130}
131
132#[cfg(all(test, feature = "circuit"))]
133mod tests {
134 use ark_bls12_381::Bls12_381;
135 use ark_groth16::Groth16;
136 use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
137 use ark_snark::SNARK;
138
139 use super::*;
140 use crate::shielder::{
141 note::{compute_note, compute_parent_hash},
142 types::FrontendNote,
143 };
144
145 const MAX_PATH_LEN: u8 = 4;
146
147 fn get_circuit_with_full_input() -> DepositAndMergeRelationWithFullInput {
148 let token_id: FrontendTokenId = 1;
149
150 let old_trapdoor: FrontendTrapdoor = [17; 4];
151 let old_nullifier: FrontendNullifier = [19; 4];
152 let old_token_amount: FrontendTokenAmount = 7;
153
154 let new_trapdoor: FrontendTrapdoor = [27; 4];
155 let new_nullifier: FrontendNullifier = [87; 4];
156 let new_token_amount: FrontendTokenAmount = 10;
157
158 let token_amount: FrontendTokenAmount = 3;
159
160 let old_note = compute_note(token_id, old_token_amount, old_trapdoor, old_nullifier);
161 let new_note = compute_note(token_id, new_token_amount, new_trapdoor, new_nullifier);
162
163 let leaf_index = 5;
169
170 let zero_note = FrontendNote::default(); let sibling_note = compute_note(0, 1, [2; 4], [3; 4]); let parent_note = compute_parent_hash(sibling_note, old_note); let uncle_note = compute_note(4, 5, [6; 4], [7; 4]); let grandpa_root = compute_parent_hash(parent_note, uncle_note); let placeholder = compute_parent_hash(grandpa_root, zero_note);
178 let merkle_root = compute_parent_hash(placeholder, zero_note);
179
180 let merkle_path = vec![sibling_note, uncle_note];
181
182 DepositAndMergeRelationWithFullInput::new(
183 MAX_PATH_LEN,
184 token_id,
185 old_nullifier,
186 new_note,
187 token_amount,
188 merkle_root,
189 old_trapdoor,
190 new_trapdoor,
191 new_nullifier,
192 merkle_path,
193 leaf_index,
194 old_note,
195 old_token_amount,
196 new_token_amount,
197 )
198 }
199
200 #[test]
201 fn deposit_and_merge_constraints_correctness() {
202 let circuit = get_circuit_with_full_input();
203
204 let cs = ConstraintSystem::new_ref();
205 circuit.generate_constraints(cs.clone()).unwrap();
206
207 let is_satisfied = cs.is_satisfied().unwrap();
208 if !is_satisfied {
209 println!("{:?}", cs.which_is_unsatisfied());
210 }
211
212 assert!(is_satisfied);
213 }
214
215 #[test]
216 fn deposit_and_merge_proving_procedure() {
217 let circuit_withouth_input = DepositAndMergeRelationWithoutInput::new(MAX_PATH_LEN);
218
219 let mut rng = ark_std::test_rng();
220 let (pk, vk) =
221 Groth16::<Bls12_381>::circuit_specific_setup(circuit_withouth_input, &mut rng).unwrap();
222
223 let circuit = get_circuit_with_full_input();
224 let proof = Groth16::prove(&pk, circuit, &mut rng).unwrap();
225
226 let circuit: DepositAndMergeRelationWithPublicInput = get_circuit_with_full_input().into();
227 let input = circuit.serialize_public_input();
228 let valid_proof = Groth16::verify(&vk, &input, &proof).unwrap();
229 assert!(valid_proof);
230 }
231}