-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsk_proof.compact
More file actions
108 lines (98 loc) · 4.11 KB
/
Copy pathsk_proof.compact
File metadata and controls
108 lines (98 loc) · 4.11 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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Client-side circuit, split-prove v3.
//
// Replaces the v2 sk_proof, which paid SHA-256(sk) on every spend, with an
// open of a one-time-per-wallet Poseidon commitment `C_sk`. The canonical
// `pk = persistentHash("midnight:zswap-pk[v1]", sk)` is now proven by the
// separate `wallet_attestation.compact` circuit at wallet-setup time and
// re-verified per-spend in the Rust admission code by cross-checking the
// public outputs of both proofs. See
// /Users/adgl/.claude/plans/currently-split-prove-works-and-tingly-breeze.md
// for the soundness argument.
//
// What this circuit proves per spend:
// 1. The wallet knows `(sk, r)` that opens the registered `C_sk` —
// `commitmentSk = transientHash("midnight:sk-commit[v1]", sk, r)`.
// 2. The canonical nullifier was derived from *that* same `sk` —
// `nullifier = persistentHash("midnight:zswap-cn[v1]", coin, true, sk)`.
// 3. The same `pk` carried in the handoff is the coin owner key used by
// the server's `coinBindingTag` and `coinCommitment` —
// `coinBindingTag = transientHash("midnight:zswap-split-coin[v1]", coin, pk)`.
//
// `pk` is disclosed as a witness (not recomputed from sk); the admission
// verifier asserts it equals the attestation's `pk` public output, and the
// attestation already binds that `pk` to the unique `sk` opening `C_sk`.
//
// Soundness summary:
// * Poseidon binding on `(sk, r)` ⇒ at most one `sk` opens the registered
// `C_sk` (computationally, under transientHash collision resistance).
// * The same `sk` bit witnesses are consumed by the SHA-256 nullifier
// gadget AND by the Poseidon C_sk gadget — Compact's natural compilation
// reuses the constrain_bits-decomposed limbs of `sk.bytes` across both.
// * The canonical nullifier formula is byte-identical to stock Zswap, so
// a stock spend and a split spend of the same coin still collide in the
// ledger nullifier set.
import CompactStandardLibrary;
struct ZswapCoinSecretKey {
bytes: Bytes<32>;
}
struct SplitCoinBindingPreimage {
sep: Field,
coin: ShieldedCoinInfo,
publicKey: ZswapCoinPublicKey,
}
struct CoinPreimage {
sep: Bytes<21>,
info: ShieldedCoinInfo,
dataType: Boolean,
data: Bytes<32>,
}
struct SkCommitPreimage {
sep: Field,
secretKey: ZswapCoinSecretKey,
blinding: Field,
}
// Public outputs: pk, nullifier, coinBindingTag, commitmentSk.
ledger publicKey: ZswapCoinPublicKey;
ledger nullifier: Bytes<32>;
ledger coinBindingTag: Field;
ledger commitmentSk: Field;
export circuit sk_prove(
sk: ZswapCoinSecretKey,
pk: ZswapCoinPublicKey,
r: Field,
coin: ShieldedCoinInfo
): [] {
// 1. Disclose pk from witness. NOT re-derived from sk in-circuit —
// the attestation circuit (run once at wallet setup) proves
// `pk = persistentHash(sep, sk)` and the admission verifier
// cross-checks `attested_pk == this disclosed pk`.
publicKey = disclose(pk);
// 2. Canonical Zswap nullifier — byte-identical to stock so split and
// non-split spends of the same coin collide in the same on-ledger
// nullifier set. Uses the bit-decomposed `sk` witnesses that Compact
// also feeds to the C_sk hash below.
nullifier = disclose(persistentHash<CoinPreimage>(
CoinPreimage {
sep: "midnight:zswap-cn[v1]",
info: coin,
dataType: true,
data: sk.bytes,
}));
// 3. Coin-binding tag — unchanged from v2. Links this client proof and
// the server's spend-split proof to the same `(coin, pk)`.
coinBindingTag = disclose(transientHash<SplitCoinBindingPreimage>(
SplitCoinBindingPreimage {
sep: "midnight:zswap-split-coin[v1]" as Field,
coin: coin,
publicKey: pk,
}));
// 4. Open the per-wallet Poseidon commitment to `sk`. Recomputed in-circuit
// from witness `(sk, r)`; the admission verifier cross-checks this
// against the attestation's `commitmentSk` public output. Cheaper than
// a Pedersen open by ~60× on this Compact lowering (see docs/spike-results.md).
commitmentSk = disclose(transientHash<SkCommitPreimage>(SkCommitPreimage {
sep: "midnight:sk-commit[v1]" as Field,
secretKey: sk,
blinding: r,
}));
}