forked from Dshield-xyz/Dshield
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.nr
More file actions
90 lines (78 loc) · 3.21 KB
/
Copy pathmain.nr
File metadata and controls
90 lines (78 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use dep::poseidon::poseidon2::Poseidon2;
global TREE_DEPTH: u32 = 20;
// Domain separation tags for Poseidon2 hashing
global LEAF_DOMAIN: Field = 0x4c454146; // "LEAF" in hex (right-aligned)
global KYC_DOMAIN: Field = 0x4b5943; // "KYC" in hex (right-aligned to 3 bytes)
fn hash2(a: Field, b: Field) -> Field {
Poseidon2::hash([a, b], 2)
}
fn hash3(a: Field, b: Field, c: Field) -> Field {
Poseidon2::hash([a, b, c], 3)
}
fn hash_leaf(nullifier: Field, secret: Field) -> Field {
hash3(LEAF_DOMAIN, nullifier, secret)
}
fn hash_kyc(kyc_preimage: Field) -> Field {
hash3(KYC_DOMAIN, kyc_preimage, 0)
}
fn constrain_bit(bit: Field) {
assert(bit * (1 - bit) == 0);
}
fn compute_root(leaf: Field, path_siblings: [Field; TREE_DEPTH], path_bits: [Field; TREE_DEPTH]) -> Field {
let mut cur = leaf;
for i in 0..TREE_DEPTH {
let sib = path_siblings[i];
let bit = path_bits[i];
constrain_bit(bit);
if bit == 0 {
cur = hash2(cur, sib);
} else {
cur = hash2(sib, cur);
}
}
cur
}
pub fn main(
// Public inputs - visible to the verifier (auditor/regulator)
merkle_root: pub Field,
kyc_hash: pub Field,
disclosed_amount: pub Field,
auditor_key: pub Field,
// Private inputs - never leave the user's device
kyc_preimage: Field,
nullifier: Field,
secret: Field,
amount: Field,
path_siblings: [Field; TREE_DEPTH],
path_bits: [Field; TREE_DEPTH],
) {
// 1. KYC proof: user knows the preimage behind the registered kyc_hash
let computed_kyc = hash_kyc(kyc_preimage);
assert(computed_kyc == kyc_hash);
// 2. Wallet authorization: user owns a note in the shielded pool
let leaf = hash_leaf(nullifier, secret);
let computed_root = compute_root(leaf, path_siblings, path_bits);
assert(computed_root == merkle_root);
// 3. Selective disclosure: `amount`/`disclosed_amount` are NOT bound to the
// note above (the leaf is hash3(LEAF_DOMAIN, nullifier, secret) -- amount plays no
// part in it), so this assert alone proves nothing about the real note;
// a prover could pick any two equal values here. `disclosure_commitment`
// and `expected` are computed but deliberately unused for the same
// reason: hashing an unconstrained `amount` with anything doesn't
// constrain it either.
//
// The real amount binding happens on-chain: DShield pools are
// fixed-denomination (every note in a given pool has the same
// deposit_amount), so the compliance contract's `amount_for_root`
// looks up which configured pool `merkle_root` belongs to (via
// cross-contract `is_known_root`) and rejects the proof unless
// `disclosed_amount` equals that pool's actual `get_deposit_amount()`.
// See contracts/compliance/src/lib.rs `verify_compliance`. This
// mirrors how the shielded_pool circuit's `recipient` binding is a
// no-op in-circuit and enforced by the pool contract instead.
let disclosure_commitment = hash3(amount, auditor_key, nullifier);
let expected = hash2(disclosed_amount, auditor_key);
assert(amount == disclosed_amount);
let _ = disclosure_commitment;
let _ = expected;
}