@@ -19,11 +19,228 @@ use solana_sdk::{
1919use std:: collections:: HashMap ;
2020use zk_nullifier:: { BATCH_SIZE , NULLIFIER_PREFIX } ;
2121
22+ #[ link( name = "circuit_single" , kind = "static" ) ]
23+ extern "C" { }
24+
2225#[ link( name = "circuit_batch" , kind = "static" ) ]
2326extern "C" { }
2427
28+ rust_witness:: witness!( nullifier1) ;
2529rust_witness:: witness!( nullifier4) ;
2630
31+ // ============================================================================
32+ // Shared helpers
33+ // ============================================================================
34+
35+ fn generate_random_secret ( ) -> [ u8 ; 32 ] {
36+ let random_keypair = Keypair :: new ( ) ;
37+ let mut secret = [ 0u8 ; 32 ] ;
38+ secret[ 1 ..32 ] . copy_from_slice ( & random_keypair. to_bytes ( ) [ 0 ..31 ] ) ;
39+ secret
40+ }
41+
42+ fn compute_nullifier ( verification_id : & [ u8 ; 32 ] , secret : & [ u8 ; 32 ] ) -> [ u8 ; 32 ] {
43+ Poseidon :: hashv ( & [ verification_id, secret] ) . unwrap ( )
44+ }
45+
46+ fn compress_proof (
47+ proof : & circom_prover:: prover:: circom:: Proof ,
48+ ) -> light_compressed_account:: instruction_data:: compressed_proof:: CompressedProof {
49+ let ( proof_a_uncompressed, proof_b_uncompressed, proof_c_uncompressed) =
50+ convert_proof ( proof) . expect ( "Failed to convert proof" ) ;
51+
52+ let ( proof_a, proof_b, proof_c) = convert_proof_to_compressed (
53+ & proof_a_uncompressed,
54+ & proof_b_uncompressed,
55+ & proof_c_uncompressed,
56+ )
57+ . expect ( "Failed to compress proof" ) ;
58+
59+ light_compressed_account:: instruction_data:: compressed_proof:: CompressedProof {
60+ a : proof_a,
61+ b : proof_b,
62+ c : proof_c,
63+ }
64+ }
65+
66+ // ============================================================================
67+ // Single nullifier test
68+ // ============================================================================
69+
70+ #[ tokio:: test]
71+ async fn test_create_nullifier ( ) {
72+ let config = ProgramTestConfig :: new ( true , Some ( vec ! [ ( "zk_nullifier" , zk_nullifier:: ID ) ] ) ) ;
73+ let mut rpc = LightProgramTest :: new ( config) . await . unwrap ( ) ;
74+ let payer = rpc. get_payer ( ) . insecure_clone ( ) ;
75+
76+ let address_tree_info = rpc. get_address_tree_v2 ( ) ;
77+
78+ let secret = generate_random_secret ( ) ;
79+ let verification_id = Pubkey :: new_unique ( ) . to_bytes ( ) ;
80+ let nullifier = compute_nullifier ( & verification_id, & secret) ;
81+
82+ let ( nullifier_address, _) = derive_address (
83+ & [
84+ NULLIFIER_PREFIX ,
85+ nullifier. as_slice ( ) ,
86+ verification_id. as_slice ( ) ,
87+ ] ,
88+ & address_tree_info. tree ,
89+ & zk_nullifier:: ID ,
90+ ) ;
91+
92+ let instruction = build_create_nullifier_instruction (
93+ & mut rpc,
94+ & payer,
95+ & nullifier_address,
96+ address_tree_info. clone ( ) ,
97+ & verification_id,
98+ & nullifier,
99+ & secret,
100+ )
101+ . await
102+ . unwrap ( ) ;
103+
104+ let cu = simulate_cu ( & mut rpc, & payer, & instruction) . await ;
105+ println ! ( "=== Single nullifier CU: {} ===" , cu) ;
106+
107+ rpc. create_and_send_transaction ( & [ instruction] , & payer. pubkey ( ) , & [ & payer] )
108+ . await
109+ . unwrap ( ) ;
110+
111+ let nullifier_accounts = rpc
112+ . get_compressed_accounts_by_owner ( & zk_nullifier:: ID , None , None )
113+ . await
114+ . unwrap ( ) ;
115+ assert_eq ! ( nullifier_accounts. value. items. len( ) , 1 ) ;
116+
117+ // Duplicate should fail
118+ let dup_instruction = build_create_nullifier_instruction (
119+ & mut rpc,
120+ & payer,
121+ & nullifier_address,
122+ address_tree_info,
123+ & verification_id,
124+ & nullifier,
125+ & secret,
126+ )
127+ . await
128+ . unwrap ( ) ;
129+
130+ let result = rpc
131+ . create_and_send_transaction ( & [ dup_instruction] , & payer. pubkey ( ) , & [ & payer] )
132+ . await ;
133+ assert ! ( result. is_err( ) ) ;
134+ }
135+
136+ async fn build_create_nullifier_instruction < R > (
137+ rpc : & mut R ,
138+ payer : & Keypair ,
139+ address : & [ u8 ; 32 ] ,
140+ address_tree_info : light_client:: indexer:: TreeInfo ,
141+ verification_id : & [ u8 ; 32 ] ,
142+ nullifier : & [ u8 ; 32 ] ,
143+ secret : & [ u8 ; 32 ] ,
144+ ) -> Result < Instruction , RpcError >
145+ where
146+ R : Rpc + Indexer ,
147+ {
148+ let mut remaining_accounts = PackedAccounts :: default ( ) ;
149+ remaining_accounts. add_pre_accounts_signer ( payer. pubkey ( ) ) ;
150+ let config = SystemAccountMetaConfig :: new ( zk_nullifier:: ID ) ;
151+ remaining_accounts. add_system_accounts_v2 ( config) ?;
152+
153+ let rpc_result = rpc
154+ . get_validity_proof (
155+ vec ! [ ] ,
156+ vec ! [ AddressWithTree {
157+ address: * address,
158+ tree: address_tree_info. tree,
159+ } ] ,
160+ None ,
161+ )
162+ . await ?
163+ . value ;
164+
165+ let packed_address_tree_accounts = rpc_result
166+ . pack_tree_infos ( & mut remaining_accounts)
167+ . address_trees ;
168+
169+ let output_state_tree_index = rpc
170+ . get_random_state_tree_info ( ) ?
171+ . pack_output_tree_index ( & mut remaining_accounts) ?;
172+
173+ let zk_proof = generate_single_zk_proof ( verification_id, nullifier, secret) ;
174+
175+ let ( remaining_accounts_metas, system_accounts_offset, _) = remaining_accounts. to_account_metas ( ) ;
176+
177+ let instruction_data = zk_nullifier:: instruction:: CreateNullifier {
178+ proof : rpc_result. proof ,
179+ address_tree_info : packed_address_tree_accounts[ 0 ] ,
180+ output_state_tree_index,
181+ system_accounts_offset : system_accounts_offset as u8 ,
182+ zk_proof,
183+ verification_id : * verification_id,
184+ nullifier : * nullifier,
185+ } ;
186+
187+ let accounts = zk_nullifier:: accounts:: CreateNullifierAccounts {
188+ signer : payer. pubkey ( ) ,
189+ } ;
190+
191+ Ok ( Instruction {
192+ program_id : zk_nullifier:: ID ,
193+ accounts : [
194+ accounts. to_account_metas ( None ) ,
195+ remaining_accounts_metas,
196+ ]
197+ . concat ( ) ,
198+ data : instruction_data. data ( ) ,
199+ } )
200+ }
201+
202+ fn generate_single_zk_proof (
203+ verification_id : & [ u8 ; 32 ] ,
204+ nullifier : & [ u8 ; 32 ] ,
205+ secret : & [ u8 ; 32 ] ,
206+ ) -> light_compressed_account:: instruction_data:: compressed_proof:: CompressedProof {
207+ let manifest_dir = env ! ( "CARGO_MANIFEST_DIR" ) ;
208+ let zkey_path = format ! ( "{}/../../build/nullifier_1_final.zkey" , manifest_dir) ;
209+
210+ let mut proof_inputs = HashMap :: new ( ) ;
211+ proof_inputs. insert (
212+ "verification_id" . to_string ( ) ,
213+ vec ! [ BigUint :: from_bytes_be( verification_id) . to_string( ) ] ,
214+ ) ;
215+ proof_inputs. insert (
216+ "nullifier" . to_string ( ) ,
217+ vec ! [ BigUint :: from_bytes_be( nullifier) . to_string( ) ] ,
218+ ) ;
219+ proof_inputs. insert (
220+ "secret" . to_string ( ) ,
221+ vec ! [ BigUint :: from_bytes_be( secret) . to_string( ) ] ,
222+ ) ;
223+
224+ let circuit_inputs = serde_json:: to_string ( & proof_inputs) . unwrap ( ) ;
225+ let proof = CircomProver :: prove (
226+ ProofLib :: Arkworks ,
227+ WitnessFn :: RustWitness ( nullifier1_witness) ,
228+ circuit_inputs,
229+ zkey_path. clone ( ) ,
230+ )
231+ . expect ( "Proof generation failed" ) ;
232+
233+ let is_valid = CircomProver :: verify ( ProofLib :: Arkworks , proof. clone ( ) , zkey_path)
234+ . expect ( "Proof verification failed" ) ;
235+ assert ! ( is_valid) ;
236+
237+ compress_proof ( & proof. proof )
238+ }
239+
240+ // ============================================================================
241+ // Batch nullifier test
242+ // ============================================================================
243+
27244#[ tokio:: test]
28245async fn test_create_batch_nullifier ( ) {
29246 let config = ProgramTestConfig :: new ( true , Some ( vec ! [ ( "zk_nullifier" , zk_nullifier:: ID ) ] ) ) ;
@@ -105,17 +322,6 @@ async fn test_create_batch_nullifier() {
105322 assert ! ( result. is_err( ) ) ;
106323}
107324
108- fn generate_random_secret ( ) -> [ u8 ; 32 ] {
109- let random_keypair = Keypair :: new ( ) ;
110- let mut secret = [ 0u8 ; 32 ] ;
111- secret[ 1 ..32 ] . copy_from_slice ( & random_keypair. to_bytes ( ) [ 0 ..31 ] ) ;
112- secret
113- }
114-
115- fn compute_nullifier ( verification_id : & [ u8 ; 32 ] , secret : & [ u8 ; 32 ] ) -> [ u8 ; 32 ] {
116- Poseidon :: hashv ( & [ verification_id, secret] ) . unwrap ( )
117- }
118-
119325async fn build_create_batch_nullifier_instruction < R > (
120326 rpc : & mut R ,
121327 payer : & Keypair ,
@@ -195,7 +401,6 @@ fn generate_batch_zk_proof(
195401 nullifiers : & [ [ u8 ; 32 ] ; BATCH_SIZE ] ,
196402 secrets : & [ [ u8 ; 32 ] ; BATCH_SIZE ] ,
197403) -> light_compressed_account:: instruction_data:: compressed_proof:: CompressedProof {
198- // CARGO_MANIFEST_DIR = programs/zk-nullifier, build is at workspace root
199404 let manifest_dir = env ! ( "CARGO_MANIFEST_DIR" ) ;
200405 let zkey_path = format ! ( "{}/../../build/nullifier_4_final.zkey" , manifest_dir) ;
201406
@@ -232,24 +437,3 @@ fn generate_batch_zk_proof(
232437
233438 compress_proof ( & proof. proof )
234439}
235-
236- fn compress_proof (
237- proof : & circom_prover:: prover:: circom:: Proof ,
238- ) -> light_compressed_account:: instruction_data:: compressed_proof:: CompressedProof {
239- let ( proof_a_uncompressed, proof_b_uncompressed, proof_c_uncompressed) =
240- convert_proof ( proof) . expect ( "Failed to convert proof" ) ;
241-
242- let ( proof_a, proof_b, proof_c) = convert_proof_to_compressed (
243- & proof_a_uncompressed,
244- & proof_b_uncompressed,
245- & proof_c_uncompressed,
246- )
247- . expect ( "Failed to compress proof" ) ;
248-
249- light_compressed_account:: instruction_data:: compressed_proof:: CompressedProof {
250- a : proof_a,
251- b : proof_b,
252- c : proof_c,
253- }
254- }
255-
0 commit comments