Skip to content

Commit f48ebd1

Browse files
author
tilo-14
committed
fix(zk-nullifier): consolidate tests and add CI sleep
- Merge test_batch.rs and test_single.rs into single test.rs - Add 2s sleep after airdrop in CI to prevent race condition
1 parent 846000e commit f48ebd1

3 files changed

Lines changed: 218 additions & 261 deletions

File tree

.github/workflows/rust-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,6 @@ jobs:
113113
solana-test-validator --reset &
114114
sleep 5
115115
solana airdrop 100
116+
sleep 2
116117
solana program deploy target/deploy/*.so
117118
npm run test:ts

zk/zk-nullifier/programs/zk-nullifier/tests/test_batch.rs renamed to zk/zk-nullifier/programs/zk-nullifier/tests/test.rs

Lines changed: 217 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,228 @@ use solana_sdk::{
1919
use std::collections::HashMap;
2020
use 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")]
2326
extern "C" {}
2427

28+
rust_witness::witness!(nullifier1);
2529
rust_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]
28245
async 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-
119325
async 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

Comments
 (0)