@openzeppelin/compact-contracts 0.4.0 → 0.5.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/access/AccessControl.compact +8 -9
- package/access/Ownable.compact +21 -20
- package/access/ShieldedAccessControl.compact +20 -19
- package/access/ZOwnablePK.compact +5 -3
- package/crypto/Ecdh.compact +153 -0
- package/crypto/EcdhMask.compact +146 -84
- package/crypto/Ecdsa.compact +1 -1
- package/crypto/Eip712.compact +1 -1
- package/crypto/ElGamal.compact +13 -13
- package/math/UintN.compact +82 -0
- package/multisig/ForwarderPrivate.compact +6 -17
- package/multisig/ForwarderShielded.compact +1 -1
- package/multisig/ForwarderUnshielded.compact +1 -1
- package/multisig/ProposalManager.compact +127 -65
- package/multisig/Secp256k1EcdsaMultisig.compact +1 -1
- package/multisig/ShieldedTreasury.compact +8 -20
- package/multisig/ShieldedTreasuryStateless.compact +9 -21
- package/multisig/Signer.compact +1 -1
- package/multisig/UnshieldedTreasury.compact +11 -24
- package/multisig/examples/ForwarderPrivateExample.compact +2 -3
- package/multisig/examples/ForwarderShieldedExample.compact +1 -1
- package/multisig/examples/ForwarderUnshieldedExample.compact +1 -1
- package/multisig/examples/{ShieldedMultiSigV2Example.compact → NativeShieldedStatelessTreasuryExample.compact} +23 -23
- package/multisig/examples/NativeShieldedTokenIssuerExample.compact +1 -1
- package/multisig/presets/NativeShieldedStatelessTreasury.compact +443 -0
- package/multisig/presets/NativeShieldedTokenIssuer.compact +2 -5
- package/package.json +11 -11
- package/security/Allowlist.compact +1 -1
- package/security/Blocklist.compact +1 -1
- package/security/Initializable.compact +3 -1
- package/security/Pausable.compact +1 -1
- package/token/ConfidentialFungibleToken.compact +22 -70
- package/token/ConfidentialNoteFungibleToken.compact +741 -0
- package/token/FungibleToken.compact +36 -30
- package/token/MultiToken.compact +27 -21
- package/token/NativeShieldedToken.compact +4 -12
- package/token/NativeShieldedTokenCore.compact +1 -1
- package/token/NativeShieldedTokenFamily.compact +4 -4
- package/token/NonFungibleToken.compact +28 -22
- package/token/examples/ZOwnableNativeShieldedTokenExample.compact +1 -1
- package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
- package/token/extensions/ConfidentialNoteFungibleTokenDelivery.compact +274 -0
- package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
- package/token/extensions/NativeShieldedTokenFamilyPublicSupply.compact +1 -1
- package/token/extensions/NativeShieldedTokenPublicSupply.compact +1 -1
- package/token/extensions/NativeShieldedTokenPublicSupplyCore.compact +1 -1
- package/token/presets/ZOwnableNativeShieldedToken.compact +1 -1
- package/utils/EvmAbi.compact +1 -1
- package/utils/Utils.compact +1 -1
- package/multisig/RecipientTypes.compact +0 -48
- package/multisig/presets/ShieldedMultiSigV2.compact +0 -295
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/ConfidentialNoteFungibleToken.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.26.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module ConfidentialNoteFungibleToken<#depth>
|
|
8
|
+
* @description
|
|
9
|
+
* A note-based confidential token: amounts, sender, and recipient are hidden
|
|
10
|
+
* from the public ledger.
|
|
11
|
+
*
|
|
12
|
+
* @dev Notation:
|
|
13
|
+
* - `H(...)`: `persistentHash` (SHA-256), a `Bytes<32>` digest.
|
|
14
|
+
* - `Hf(...)`: `H` reduced to a `Field` via `degradeToTransient`.
|
|
15
|
+
* - NOTE: a UTxO `(value, nonce)`. A balance is the sum of one's unspent notes.
|
|
16
|
+
* - `pk = Hf(domain, salt, sk)`: the owner identity, `salt` being the
|
|
17
|
+
* deployment's `_instanceSalt`.
|
|
18
|
+
* - `cm = H(domain, value, nonce, pk)`: the commitment stored in the tree.
|
|
19
|
+
* - `nf = H(domain, nonce)`: the nullifier that marks a note spent.
|
|
20
|
+
* - `tag = H(domain, nonce)`: the issued tag that reserves a nonce at mint.
|
|
21
|
+
*
|
|
22
|
+
* @dev Derivations:
|
|
23
|
+
* `H` hashes the named preimage struct in Compact's encoding, not a byte
|
|
24
|
+
* concatenation. Do not reimplement it: `derivePk`, `commitOf`, `nullifierOf`
|
|
25
|
+
* and `issuedTagOf` are exported pure circuits, and the compatibility suite
|
|
26
|
+
* pins their vectors. `derivePk` takes the deployment's `_instanceSalt` as
|
|
27
|
+
* `salt`. A wrong derivation yields notes that cannot be found and identities
|
|
28
|
+
* that cannot spend. No pure circuit covers `_mint`'s nonce derivation:
|
|
29
|
+
* the caller receives the note, so nothing off-chain recomputes it.
|
|
30
|
+
*
|
|
31
|
+
* @dev Parameterization:
|
|
32
|
+
* `#depth` is the commitment tree's depth, fixed at import time
|
|
33
|
+
* (`import "./ConfidentialNoteFungibleToken"<32> prefix Core_;`). It sets
|
|
34
|
+
* capacity at `2^depth` notes and the length of every Merkle path, so it is
|
|
35
|
+
* part of the deployed wire format: two instantiations at different depths are
|
|
36
|
+
* different contracts and share no notes.
|
|
37
|
+
*
|
|
38
|
+
* @dev Compatibility:
|
|
39
|
+
* The wire format is the domain tags (`pk`'s included), the preimage struct
|
|
40
|
+
* layouts, and `depth`. Changing any of them orphans every existing note. The
|
|
41
|
+
* compatibility suite pins all three.
|
|
42
|
+
*
|
|
43
|
+
* @notice Privacy:
|
|
44
|
+
* - Public, per tx:
|
|
45
|
+
* - The entry point, so the operation type.
|
|
46
|
+
* - One `nf` per note spent.
|
|
47
|
+
* - One `tag` and one opaque leaf digest per note created.
|
|
48
|
+
* - The Merkle root the spend proves against.
|
|
49
|
+
* - The transcript shape, which does not vary with amount or recipient.
|
|
50
|
+
* - Public, as state:
|
|
51
|
+
* - The init flag, the instance salt, the commitment tree, the nullifier
|
|
52
|
+
* set, the issued-nonce set.
|
|
53
|
+
* - No balances, no accounts, no supply.
|
|
54
|
+
* - Private:
|
|
55
|
+
* - Amounts.
|
|
56
|
+
* - Sender and recipient `pk`s, even under `sk` reuse: `pk` is
|
|
57
|
+
* domain-tagged and salted by `_instanceSalt`, so no other contract's
|
|
58
|
+
* identity for the same `sk` derives it.
|
|
59
|
+
* - Nonces and secret keys.
|
|
60
|
+
* - Which leaf a spend consumes, within the leaves present at the
|
|
61
|
+
* disclosed root. A stale root narrows that set to older leaves, and a
|
|
62
|
+
* rarely refreshed one fingerprints the wallet.
|
|
63
|
+
* - The link between a mint and its spend, absent the nonce.
|
|
64
|
+
* - Counterparty: whoever knows a note's nonce derives its `tag` and `nf`, so
|
|
65
|
+
* sees WHEN it is minted and spent, not by whom or for how much.
|
|
66
|
+
* - Auditability: no view key. Disclosure is per note, by sharing its nonce.
|
|
67
|
+
* Supply is not tracked; a composer adds it.
|
|
68
|
+
*
|
|
69
|
+
* @notice Security:
|
|
70
|
+
* - Nonces are spend-critical: `nf` depends on the nonce alone, so anyone who
|
|
71
|
+
* knows a nonce derives the same nullifier and can race the owner for the
|
|
72
|
+
* single spend. This is also what lets a composer offer escrow-free
|
|
73
|
+
* clawback: two spends race for one `nf`, first to land wins.
|
|
74
|
+
* - Freshness: `wit_ConfidentialNoteNonceRandomness` MUST return a fresh
|
|
75
|
+
* secret seed per call. Outputs bind their context (the spent `nf`, or the
|
|
76
|
+
* recipient for
|
|
77
|
+
* `_mint`), and `_mintNote` rejects an already-issued nonce, so a repeated
|
|
78
|
+
* seed fails loudly instead of producing a dead note.
|
|
79
|
+
* - Ownership: a spend proves `cm` binds `_spenderPk()`; no secret, no spend.
|
|
80
|
+
* - No authorization on `_` circuits: `_mint`, `_mintNote`, `_transfer`,
|
|
81
|
+
* `_burn`, `_consumeNote` are ungated. Re-exporting one from a deployed
|
|
82
|
+
* contract is a permissionless mint or spend. The composer gates them.
|
|
83
|
+
*
|
|
84
|
+
* @notice Concurrency:
|
|
85
|
+
* - Tree and set inserts commute; two calls collide only where they pin the
|
|
86
|
+
* same key.
|
|
87
|
+
* - Spends pin `_nullifiers.member(nf)`: only two spends of the SAME note
|
|
88
|
+
* conflict.
|
|
89
|
+
* - Mints pin `_issuedNonces.member(tag)`: only a duplicate nonce conflicts.
|
|
90
|
+
* - `checkRoot` on the historic tree pins "root in history", so a proof built
|
|
91
|
+
* against a stale root still lands after concurrent inserts.
|
|
92
|
+
* - Net: calls on distinct notes in one block all land; see the concurrency
|
|
93
|
+
* suite for the exhaustive pairwise matrix.
|
|
94
|
+
*
|
|
95
|
+
* @notice Scope:
|
|
96
|
+
* Note machinery only: no roles. The one setup step is `initialize(salt)`,
|
|
97
|
+
* called from the composer's constructor; every mint and spend requires it.
|
|
98
|
+
* `transfer` and `burn` are self-gated by `wit_ConfidentialNoteSK` and the
|
|
99
|
+
* caller's randomness witness.
|
|
100
|
+
* Created notes return to the caller as a private result and reach recipients
|
|
101
|
+
* out of band.
|
|
102
|
+
*
|
|
103
|
+
* @notice Client-side duties:
|
|
104
|
+
* - Identity: `pk = derivePk(sk, _instanceSalt)` is the owner's identity on
|
|
105
|
+
* this deployment only. Salted per deployment and domain-separated from
|
|
106
|
+
* both the `ZswapCoinPublicKey` and the public modules' `computeAccountId`,
|
|
107
|
+
* it links no identities when `sk` is reused across contracts. Read the
|
|
108
|
+
* salt from the ledger, or take it from the deployer for a constructor-time
|
|
109
|
+
* mint. Derive `sk` from the wallet seed under a module-specific domain so
|
|
110
|
+
* one seed backup covers it, and share `pk` out of band.
|
|
111
|
+
* - Implement the four witnesses: `wit_ConfidentialNoteSK`,
|
|
112
|
+
* `wit_ConfidentialNoteInputNote`, `wit_ConfidentialNotePath`,
|
|
113
|
+
* `wit_ConfidentialNoteNonceRandomness`.
|
|
114
|
+
* - Keep every note and its leaf position. There is no memo and no viewing
|
|
115
|
+
* key, so a chain rescan finds nothing: the seed restores identity, not
|
|
116
|
+
* balance. Notes need their own backup.
|
|
117
|
+
* - Deliver notes to recipients over a channel you provide; the contract has
|
|
118
|
+
* no inbox. A recipient should confirm the leaf digest is in the tree
|
|
119
|
+
* before accepting.
|
|
120
|
+
*
|
|
121
|
+
* @notice Scalability:
|
|
122
|
+
* - Capacity: `2^depth` notes per deployment; every output, including a
|
|
123
|
+
* zero-value change note, takes a leaf.
|
|
124
|
+
* - State growth: `_nullifiers` and `_issuedNonces` gain one entry per note,
|
|
125
|
+
* and the historic tree keeps every past root. Nothing is pruned.
|
|
126
|
+
* - One input per spend and no note merging: a balance spread over N notes
|
|
127
|
+
* needs N spends, and every transfer adds a change note.
|
|
128
|
+
* - Throughput follows the concurrency rules above: calls on distinct notes
|
|
129
|
+
* commute.
|
|
130
|
+
* - Wallets need no chain scanning, since notes arrive out of band, but must
|
|
131
|
+
* track Merkle paths, so an indexer over all leaves is required. Fetch the
|
|
132
|
+
* path right before proving: the root it yields is public.
|
|
133
|
+
*
|
|
134
|
+
* @dev Performance:
|
|
135
|
+
* - Proving cost is per call and does not grow with the tree: the path is
|
|
136
|
+
* always `depth` deep, and each level costs a fixed number of rows.
|
|
137
|
+
* - Cost is dominated by SHA-256. Every output pays the nonce, `cm` and `tag`
|
|
138
|
+
* hashes; a spend adds the input `cm`, `nf` and the path on top.
|
|
139
|
+
* - `transfer` is the largest circuit. Per-circuit `k` and rows are on each
|
|
140
|
+
* `@constraints` line.
|
|
141
|
+
*
|
|
142
|
+
* @dev Composition:
|
|
143
|
+
* Value creation has no default gate. `_mint` / `_mintNote` are ungated, and
|
|
144
|
+
* the composer decides who may create value. The `_` circuits accept
|
|
145
|
+
* caller-built notes so a composer can run its own nonce policy; `_mint` uses
|
|
146
|
+
* the core default. A constructor cannot return, so a constructor-time mint
|
|
147
|
+
* is `_mintNote` with a deployer-built note unless the same constructor
|
|
148
|
+
* records the note on chain encrypted to its owner.
|
|
149
|
+
*/
|
|
150
|
+
module ConfidentialNoteFungibleToken<#depth> {
|
|
151
|
+
import CompactStandardLibrary;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @description A unit of value. Ownership is not a field: a note belongs to
|
|
155
|
+
* whoever's `pk` was bound into its commitment (see `commitOf`).
|
|
156
|
+
* @type {Uint<128>} value - The amount the note carries.
|
|
157
|
+
* @type {Field} nonce - The commitment's hiding entropy and the sole
|
|
158
|
+
* nullifier preimage, so it is spend-critical.
|
|
159
|
+
*/
|
|
160
|
+
export struct Note {
|
|
161
|
+
value: Uint<128>;
|
|
162
|
+
nonce: Field;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @description Domain-separated preimage of an owner identity.
|
|
167
|
+
* @type {Bytes<32>} domain - The pk domain tag.
|
|
168
|
+
* @type {Bytes<32>} salt - The deployment salt.
|
|
169
|
+
* @type {Bytes<32>} sk - The owner's spend secret.
|
|
170
|
+
*/
|
|
171
|
+
struct PkPreimage {
|
|
172
|
+
domain: Bytes<32>;
|
|
173
|
+
salt: Bytes<32>;
|
|
174
|
+
sk: Bytes<32>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @description Domain-separated preimage of a note commitment.
|
|
179
|
+
* @type {Bytes<32>} domain - The commitment domain tag.
|
|
180
|
+
* @type {Uint<128>} value - The note's value.
|
|
181
|
+
* @type {Field} nonce - The note's nonce.
|
|
182
|
+
* @type {Field} pk - The owner's spend identity.
|
|
183
|
+
*/
|
|
184
|
+
struct CommitPreimage {
|
|
185
|
+
domain: Bytes<32>;
|
|
186
|
+
value: Uint<128>;
|
|
187
|
+
nonce: Field;
|
|
188
|
+
pk: Field;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @description Preimage of a nullifier or an issued tag. The owner is
|
|
193
|
+
* deliberately absent; `domain` alone keeps the two from colliding.
|
|
194
|
+
* @type {Bytes<32>} domain - The nullifier or issued-tag domain tag.
|
|
195
|
+
* @type {Field} nonce - The note's nonce.
|
|
196
|
+
*/
|
|
197
|
+
struct NullifierPreimage {
|
|
198
|
+
domain: Bytes<32>;
|
|
199
|
+
nonce: Field;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @description Domain-separated preimage of a derived output nonce.
|
|
204
|
+
* @type {Bytes<32>} seed - The caller's per-invocation randomness.
|
|
205
|
+
* @type {Bytes<32>} domain - The nonce domain tag.
|
|
206
|
+
* @type {Bytes<32>} slot - The per-output tag.
|
|
207
|
+
* @type {Field} binder - What the output is tied to.
|
|
208
|
+
*/
|
|
209
|
+
struct NoncePreimage {
|
|
210
|
+
seed: Bytes<32>;
|
|
211
|
+
domain: Bytes<32>;
|
|
212
|
+
slot: Bytes<32>;
|
|
213
|
+
binder: Field;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @description Initialization flag, tracked per-module so that two modules
|
|
218
|
+
* sharing a transitive dependency do not share one flag.
|
|
219
|
+
*/
|
|
220
|
+
export sealed ledger _isInitialized: Boolean;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @description The salt every `pk` on this deployment is derived under. Set
|
|
224
|
+
* once by `initialize`.
|
|
225
|
+
* @type {Bytes<32>} _instanceSalt - The deployment salt.
|
|
226
|
+
*/
|
|
227
|
+
export sealed ledger _instanceSalt: Bytes<32>;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @description Append-only tree of note commitments: what value exists. It
|
|
231
|
+
* does not prevent double-spends; that is `_nullifiers`' job.
|
|
232
|
+
* @dev Historic, not plain:
|
|
233
|
+
* `checkRoot` accepts any past root, so a proof survives concurrent inserts.
|
|
234
|
+
* @type {HistoricMerkleTree<depth, Bytes<32>>} _commitments - The commitment
|
|
235
|
+
* tree, holding up to `2^depth` leaves.
|
|
236
|
+
*/
|
|
237
|
+
export ledger _commitments: HistoricMerkleTree<depth, Bytes<32>>;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @description Published nullifiers: what has been spent. Unlinkable to any
|
|
241
|
+
* commitment, owner, or value, so to an observer who does not hold the note
|
|
242
|
+
* the set reveals only a spend count. See the header's Privacy notice for
|
|
243
|
+
* what a nonce-holder sees.
|
|
244
|
+
* @type {Set<Bytes<32>>} _nullifiers - The spent-note nullifier set.
|
|
245
|
+
*/
|
|
246
|
+
export ledger _nullifiers: Set<Bytes<32>>;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @description One tag per note ever committed. Separate from `_nullifiers`
|
|
250
|
+
* so that set keeps meaning "spent". See `_mintNote`.
|
|
251
|
+
* @type {Set<Bytes<32>>} _issuedNonces - The issued-tag set.
|
|
252
|
+
*/
|
|
253
|
+
export ledger _issuedNonces: Set<Bytes<32>>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @witness wit_ConfidentialNoteSK
|
|
257
|
+
* @description Returns the caller's spend secret, from which the circuits
|
|
258
|
+
* derive the identity `pk = derivePk(sk, _instanceSalt)` that owns notes.
|
|
259
|
+
*
|
|
260
|
+
* @returns {Bytes<32>} secretKey - A 32-byte cryptographically secure random
|
|
261
|
+
* value.
|
|
262
|
+
*/
|
|
263
|
+
witness wit_ConfidentialNoteSK(): Bytes<32>;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @witness wit_ConfidentialNoteInputNote
|
|
267
|
+
* @description Returns the note the next spend consumes. The wallet chooses
|
|
268
|
+
* it; the circuit proves it exists and is unspent.
|
|
269
|
+
*
|
|
270
|
+
* @returns {Note} inputNote - The note to consume.
|
|
271
|
+
*/
|
|
272
|
+
witness wit_ConfidentialNoteInputNote(): Note;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @witness wit_ConfidentialNotePath
|
|
276
|
+
* @description Returns the Merkle path proving `cm` is a leaf of the
|
|
277
|
+
* commitment tree. The path stays witness, so only membership is proved.
|
|
278
|
+
*
|
|
279
|
+
* @param {Bytes<32>} cm - The input note's commitment.
|
|
280
|
+
* @returns {MerkleTreePath<depth, Bytes<32>>} path - The `depth`-entry path
|
|
281
|
+
* from `cm` to a root the tree recognizes, current or historical.
|
|
282
|
+
*/
|
|
283
|
+
witness wit_ConfidentialNotePath(cm: Bytes<32>): MerkleTreePath<depth, Bytes<32>>;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @witness wit_ConfidentialNoteNonceRandomness
|
|
287
|
+
* @description Returns the seed the core derives its default output nonces
|
|
288
|
+
* from.
|
|
289
|
+
*
|
|
290
|
+
* @dev Freshness is mandatory:
|
|
291
|
+
* MUST be fresh and secret per call. A reused seed re-derives an identical
|
|
292
|
+
* note; `_mintNote` rejects the duplicate rather than committing it.
|
|
293
|
+
*
|
|
294
|
+
* @returns {Bytes<32>} seed - A 32-byte cryptographically secure random
|
|
295
|
+
* value.
|
|
296
|
+
*/
|
|
297
|
+
witness wit_ConfidentialNoteNonceRandomness(): Bytes<32>;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @description Sets the salt every `pk` on this deployment is derived
|
|
301
|
+
* under. Call it from the constructor; every mint and spend requires it.
|
|
302
|
+
*
|
|
303
|
+
* @dev Sealed:
|
|
304
|
+
* `_instanceSalt` is writable only during construction.
|
|
305
|
+
*
|
|
306
|
+
* Requirements:
|
|
307
|
+
*
|
|
308
|
+
* - Contract is not initialized.
|
|
309
|
+
* - `instanceSalt` is not zero.
|
|
310
|
+
*
|
|
311
|
+
* @param {Bytes<32>} instanceSalt - A strong random value unique to this
|
|
312
|
+
* deployment.
|
|
313
|
+
* @return {[]} - Empty tuple.
|
|
314
|
+
*/
|
|
315
|
+
export circuit initialize(instanceSalt: Bytes<32>): [] {
|
|
316
|
+
assertNotInitialized();
|
|
317
|
+
assert(instanceSalt != default<Bytes<32>>,
|
|
318
|
+
"ConfidentialNoteFungibleToken: instance salt must not be zero");
|
|
319
|
+
_isInitialized = true;
|
|
320
|
+
_instanceSalt = disclose(instanceSalt);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @description Asserts that the contract has been initialized.
|
|
325
|
+
*
|
|
326
|
+
* @return {[]} - Empty tuple.
|
|
327
|
+
*/
|
|
328
|
+
circuit assertInitialized(): [] {
|
|
329
|
+
assert(_isInitialized, "ConfidentialNoteFungibleToken: contract not initialized");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @description Asserts that the contract has not been initialized.
|
|
334
|
+
*
|
|
335
|
+
* @return {[]} - Empty tuple.
|
|
336
|
+
*/
|
|
337
|
+
circuit assertNotInitialized(): [] {
|
|
338
|
+
assert(!_isInitialized, "ConfidentialNoteFungibleToken: contract already initialized");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @description Mints a note of `value` to `recipientPk` with a core-derived
|
|
343
|
+
* nonce and returns it.
|
|
344
|
+
*
|
|
345
|
+
* @constraints k=15, rows=17319
|
|
346
|
+
*
|
|
347
|
+
* @notice Hidden amount and recipient:
|
|
348
|
+
* The entry point makes the mint public; the amount and the recipient stay
|
|
349
|
+
* hidden. An account-based token cannot offer this.
|
|
350
|
+
*
|
|
351
|
+
* @warning No authorization:
|
|
352
|
+
* The composer gates who may call this.
|
|
353
|
+
*
|
|
354
|
+
* @warning Constructor use:
|
|
355
|
+
* A constructor cannot return, so a note minted from one is unreachable
|
|
356
|
+
* unless the same constructor records it on chain. Otherwise build the
|
|
357
|
+
* note off-chain and call `_mintNote`.
|
|
358
|
+
*
|
|
359
|
+
* @dev Recipient-bound nonce:
|
|
360
|
+
* With no note consumed, the nonce binds `recipientPk` in place of a
|
|
361
|
+
* nullifier. A composer wanting its own nonce policy calls `_mintNote`.
|
|
362
|
+
*
|
|
363
|
+
* @param {Field} recipientPk - The identity receiving the note.
|
|
364
|
+
* @param {Uint<128>} value - The amount to create.
|
|
365
|
+
* @return {Note} - The new note, returned as a local private result.
|
|
366
|
+
*/
|
|
367
|
+
export circuit _mint(recipientPk: Field, value: Uint<128>): Note {
|
|
368
|
+
const note = Note {
|
|
369
|
+
value: value,
|
|
370
|
+
nonce: freshNonce(pad(32, "CNFT:mint"), recipientPk)
|
|
371
|
+
};
|
|
372
|
+
_mintNote(note, recipientPk);
|
|
373
|
+
return disclose(note);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @description Derives one output nonce from the caller's randomness witness,
|
|
378
|
+
* the output's slot, and what the output is bound to.
|
|
379
|
+
*
|
|
380
|
+
* @dev Slot and binder:
|
|
381
|
+
* Outputs of one invocation share a single witness call, so `slot` is what
|
|
382
|
+
* keeps them apart. `binder` scopes a repeated seed to one spend or one
|
|
383
|
+
* recipient.
|
|
384
|
+
*
|
|
385
|
+
* @param {Bytes<32>} slot - The per-output domain tag.
|
|
386
|
+
* @param {Field} binder - What the output is tied to: the consumed note's
|
|
387
|
+
* nullifier for a spend, the recipient for a mint.
|
|
388
|
+
* @return {Field} - The output note's nonce.
|
|
389
|
+
*/
|
|
390
|
+
circuit freshNonce(slot: Bytes<32>, binder: Field): Field {
|
|
391
|
+
return degradeToTransient(persistentHash<NoncePreimage>(NoncePreimage {
|
|
392
|
+
seed: wit_ConfidentialNoteNonceRandomness(),
|
|
393
|
+
domain: pad(32, "CNFT:nonce:core"),
|
|
394
|
+
slot: slot,
|
|
395
|
+
binder: binder
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @description Commits a caller-built note to `ownerPk`. No value
|
|
401
|
+
* accounting: the caller owns conservation.
|
|
402
|
+
*
|
|
403
|
+
* @constraints k=14, rows=10926
|
|
404
|
+
*
|
|
405
|
+
* @notice Nonce reservation:
|
|
406
|
+
* A duplicate nonce fails the transaction instead of committing a note born
|
|
407
|
+
* dead, so every note in the tree carries a nonce no other note has.
|
|
408
|
+
*
|
|
409
|
+
* @dev Why not `_nullifiers`:
|
|
410
|
+
* Inserting `nf` would spend the note at birth. A member-only check would
|
|
411
|
+
* disclose `nf` at mint, linking mint to spend, and would miss two unspent
|
|
412
|
+
* duplicates.
|
|
413
|
+
*
|
|
414
|
+
* @dev Weak seed:
|
|
415
|
+
* The tag is as guessable as `nf`. A guessable seed exposes at mint what
|
|
416
|
+
* `nf` exposes at spend, in return for catching the RNG break at once.
|
|
417
|
+
*
|
|
418
|
+
* @dev Why a set:
|
|
419
|
+
* A counter or the tree index would pin one shared value and serialize
|
|
420
|
+
* mints. `member` pins one key.
|
|
421
|
+
*
|
|
422
|
+
* @dev Constructor mints:
|
|
423
|
+
* The deployer builds the note and keeps its nonce, so this is the
|
|
424
|
+
* constructor path when the composer does not record it on chain.
|
|
425
|
+
*
|
|
426
|
+
* @warning Predictable nonces:
|
|
427
|
+
* The reservation is first come, first served and keyed on the nonce alone. A
|
|
428
|
+
* caller-chosen nonce that others can guess, such as a counter or tx context,
|
|
429
|
+
* lets anyone reserve it first and fail the intended mint. Derive nonces from
|
|
430
|
+
* secret randomness, and on `nonce already issued` derive a fresh one rather
|
|
431
|
+
* than retrying.
|
|
432
|
+
*
|
|
433
|
+
* @warning No authorization:
|
|
434
|
+
* The composer gates who may call this.
|
|
435
|
+
*
|
|
436
|
+
* Requirements:
|
|
437
|
+
*
|
|
438
|
+
* - Contract is initialized.
|
|
439
|
+
* - The note's nonce has not been issued before.
|
|
440
|
+
*
|
|
441
|
+
* @param {Note} note - The note to commit.
|
|
442
|
+
* @param {Field} ownerPk - The identity that will own it.
|
|
443
|
+
* @return {[]} - Empty tuple.
|
|
444
|
+
*/
|
|
445
|
+
export circuit _mintNote(note: Note, ownerPk: Field): [] {
|
|
446
|
+
assertInitialized();
|
|
447
|
+
// Reserve before committing, so a duplicate fails with nothing written.
|
|
448
|
+
const tag = issuedTagOf(note);
|
|
449
|
+
assert(!_issuedNonces.member(disclose(tag)),
|
|
450
|
+
"ConfidentialNoteFungibleToken: nonce already issued");
|
|
451
|
+
_issuedNonces.insert(disclose(tag));
|
|
452
|
+
_commitments.insert(disclose(commitOf(note, ownerPk)));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @description `tag = H(domain, nonce)`. Exported so off-chain viewers can
|
|
457
|
+
* check whether a nonce is already taken.
|
|
458
|
+
*
|
|
459
|
+
* @dev Domain separation:
|
|
460
|
+
* Only the domain distinguishes this from `nullifierOf`, so neither set can
|
|
461
|
+
* be read as the other.
|
|
462
|
+
*
|
|
463
|
+
* @param {Note} note - The note to tag.
|
|
464
|
+
* @return {Bytes<32>} - The tag published in `_issuedNonces` when the note is
|
|
465
|
+
* committed.
|
|
466
|
+
*/
|
|
467
|
+
export pure circuit issuedTagOf(note: Note): Bytes<32> {
|
|
468
|
+
return persistentHash<NullifierPreimage>(NullifierPreimage {
|
|
469
|
+
domain: pad(32, "CNFT:issued"),
|
|
470
|
+
nonce: note.nonce
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* @description `cm = H(domain, value, nonce, pk)`. Exported so off-chain
|
|
476
|
+
* viewers can recompute commitments for notes they recover.
|
|
477
|
+
*
|
|
478
|
+
* @notice Unforgeable ownership:
|
|
479
|
+
* Binding the owner is what enforces it: a non-owner's `pk` hashes to a leaf
|
|
480
|
+
* that is not in the tree, so no membership proof exists.
|
|
481
|
+
*
|
|
482
|
+
* @param {Note} note - The note to commit to.
|
|
483
|
+
* @param {Field} pk - The identity that owns the note.
|
|
484
|
+
* @return {Bytes<32>} - The hiding commitment stored in `_commitments`.
|
|
485
|
+
*/
|
|
486
|
+
export pure circuit commitOf(note: Note, pk: Field): Bytes<32> {
|
|
487
|
+
return persistentHash<CommitPreimage>(CommitPreimage {
|
|
488
|
+
domain: pad(32, "CNFT:commitment"),
|
|
489
|
+
value: note.value,
|
|
490
|
+
nonce: note.nonce,
|
|
491
|
+
pk: pk
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @description Burns `value` from the caller's input note: spends the note
|
|
497
|
+
* and re-issues only the change, so `value` leaves circulation.
|
|
498
|
+
*
|
|
499
|
+
* @constraints k=16, rows=37889
|
|
500
|
+
*
|
|
501
|
+
* @notice No supply record:
|
|
502
|
+
* The core writes no supply, so nothing on the ledger records that value was
|
|
503
|
+
* destroyed. The amount and the burner stay hidden.
|
|
504
|
+
*
|
|
505
|
+
* Requirements:
|
|
506
|
+
*
|
|
507
|
+
* - The input note is committed in the tree and unspent.
|
|
508
|
+
* - The caller owns the input note.
|
|
509
|
+
* - `value <= input.value`.
|
|
510
|
+
*
|
|
511
|
+
* @param {Uint<128>} value - The amount to burn.
|
|
512
|
+
* @return {Note} - The caller's change note, returned as a local private
|
|
513
|
+
* result.
|
|
514
|
+
*/
|
|
515
|
+
export circuit burn(value: Uint<128>): Note {
|
|
516
|
+
const pk = _spenderPk();
|
|
517
|
+
const input = _inputNote();
|
|
518
|
+
assert(input.value >= value, "ConfidentialNoteFungibleToken: insufficient note value");
|
|
519
|
+
|
|
520
|
+
// Peeked, not consumed: `_burn` is what publishes the nullifier.
|
|
521
|
+
const spendId = degradeToTransient(nullifierOf(input));
|
|
522
|
+
const changeNote = Note {
|
|
523
|
+
value: (input.value - value) as Uint<128>,
|
|
524
|
+
nonce: freshNonce(pad(32, "CNFT:change"), spendId)
|
|
525
|
+
};
|
|
526
|
+
_burn(pk, value, changeNote);
|
|
527
|
+
return disclose(changeNote);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* @description The caller's spend identity on this deployment,
|
|
532
|
+
* `derivePk(wit_ConfidentialNoteSK(), _instanceSalt)`. Exposed so a
|
|
533
|
+
* composer authorizes spends the way the core does.
|
|
534
|
+
*
|
|
535
|
+
* @constraints k=13, rows=4195
|
|
536
|
+
*
|
|
537
|
+
* @return {Field} - The caller's spend identity.
|
|
538
|
+
*/
|
|
539
|
+
export circuit _spenderPk(): Field {
|
|
540
|
+
return derivePk(wit_ConfidentialNoteSK(), _instanceSalt);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* @description `pk = Hf(domain, salt, sk)`. Exported so wallets and auditors
|
|
545
|
+
* derive identities the way the circuits do.
|
|
546
|
+
*
|
|
547
|
+
* @dev Instance salt:
|
|
548
|
+
* The circuits pass `_instanceSalt`, so one `sk` maps to an unrelated `pk`
|
|
549
|
+
* on every deployment with its own salt, and none equals the public
|
|
550
|
+
* modules' `computeAccountId(sk)`.
|
|
551
|
+
*
|
|
552
|
+
* @param {Bytes<32>} sk - The owner's spend secret.
|
|
553
|
+
* @param {Bytes<32>} salt - The deployment's `_instanceSalt`.
|
|
554
|
+
* @return {Field} - The spend identity that owns notes.
|
|
555
|
+
*/
|
|
556
|
+
export pure circuit derivePk(sk: Bytes<32>, salt: Bytes<32>): Field {
|
|
557
|
+
return degradeToTransient(persistentHash<PkPreimage>(PkPreimage {
|
|
558
|
+
domain: pad(32, "CNFT:pk"),
|
|
559
|
+
salt: salt,
|
|
560
|
+
sk: sk
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* @description Peeks at the note the next spend consumes, so a composer can
|
|
566
|
+
* size the change before spending. Reads the witness `_consumeNote` reads.
|
|
567
|
+
*
|
|
568
|
+
* @dev Peek is untrusted:
|
|
569
|
+
* A mismatch with what the composer then passes to `_transfer` / `_burn` is
|
|
570
|
+
* caught by their conservation asserts, which re-read this witness.
|
|
571
|
+
*
|
|
572
|
+
* @return {Note} - The note the next spend will consume.
|
|
573
|
+
*/
|
|
574
|
+
export circuit _inputNote(): Note {
|
|
575
|
+
return wit_ConfidentialNoteInputNote();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* @description UNGATED building block: consumes the input note owned by
|
|
580
|
+
* `spenderPk` and re-issues only `changeNote`, so `value` leaves circulation.
|
|
581
|
+
*
|
|
582
|
+
* @constraints k=15, rows=23151
|
|
583
|
+
*
|
|
584
|
+
* @warning No authorization:
|
|
585
|
+
* The composer gates who may call this.
|
|
586
|
+
*
|
|
587
|
+
* Requirements:
|
|
588
|
+
*
|
|
589
|
+
* - The input note is committed in the tree and unspent.
|
|
590
|
+
* - The input note's commitment binds `spenderPk`.
|
|
591
|
+
* - `input.value == value + changeNote.value`.
|
|
592
|
+
*
|
|
593
|
+
* @param {Field} spenderPk - The identity that owns the input note.
|
|
594
|
+
* @param {Uint<128>} value - The amount destroyed.
|
|
595
|
+
* @param {Note} changeNote - The spender's change note.
|
|
596
|
+
* @return {[]} - Empty tuple.
|
|
597
|
+
*/
|
|
598
|
+
export circuit _burn(spenderPk: Field, value: Uint<128>, changeNote: Note): [] {
|
|
599
|
+
const input = _consumeNote(spenderPk);
|
|
600
|
+
assert(input.value == value + changeNote.value,
|
|
601
|
+
"ConfidentialNoteFungibleToken: burn does not conserve value");
|
|
602
|
+
_mintNote(changeNote, spenderPk);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* @description The heart of the module: consumes the witness-supplied note
|
|
607
|
+
* owned by `ownerPk` and publishes its nullifier.
|
|
608
|
+
*
|
|
609
|
+
* @constraints k=14, rows=12124
|
|
610
|
+
*
|
|
611
|
+
* @notice Where it is used:
|
|
612
|
+
* By `_transfer` / `_burn`, and by custom spend paths such as clawback,
|
|
613
|
+
* where `ownerPk` is the target and the authorization is the caller's own.
|
|
614
|
+
*
|
|
615
|
+
* @warning No authorization:
|
|
616
|
+
* The composer gates who may call this.
|
|
617
|
+
*
|
|
618
|
+
* Requirements:
|
|
619
|
+
*
|
|
620
|
+
* - Contract is initialized.
|
|
621
|
+
* - The input note is committed in the tree, under `ownerPk`, and unspent.
|
|
622
|
+
* - The witnessed path's leaf is the input note's commitment.
|
|
623
|
+
* - The path's root is one the tree recognizes, current or historical.
|
|
624
|
+
*
|
|
625
|
+
* @param {Field} ownerPk - The identity the input note is committed to.
|
|
626
|
+
* @return {Note} - The consumed note, for the caller's value accounting.
|
|
627
|
+
*/
|
|
628
|
+
export circuit _consumeNote(ownerPk: Field): Note {
|
|
629
|
+
assertInitialized();
|
|
630
|
+
const input = wit_ConfidentialNoteInputNote();
|
|
631
|
+
|
|
632
|
+
// The root is public, but the path stays witness, so disclosing the root
|
|
633
|
+
// does not say which leaf was consumed, only that it predates the root.
|
|
634
|
+
const cm = commitOf(input, ownerPk);
|
|
635
|
+
const path = wit_ConfidentialNotePath(cm);
|
|
636
|
+
const root = disclose(merkleTreePathRoot<depth, Bytes<32>>(path));
|
|
637
|
+
assert(_commitments.checkRoot(root),
|
|
638
|
+
"ConfidentialNoteFungibleToken: input root not recognized");
|
|
639
|
+
assert(cm == path.leaf,
|
|
640
|
+
"ConfidentialNoteFungibleToken: path does not match input commitment");
|
|
641
|
+
|
|
642
|
+
const nf = nullifierOf(input);
|
|
643
|
+
assert(!_nullifiers.member(disclose(nf)),
|
|
644
|
+
"ConfidentialNoteFungibleToken: note already spent");
|
|
645
|
+
_nullifiers.insert(disclose(nf));
|
|
646
|
+
|
|
647
|
+
return input;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* @description `nf = H(domain, nonce)` — derivable by anyone who knows the
|
|
652
|
+
* nonce. Exported so off-chain viewers can watch a note's consumption.
|
|
653
|
+
*
|
|
654
|
+
* @dev Owner-free preimage:
|
|
655
|
+
* Omitting the owner secret is what makes clawback escrow-free: two parties
|
|
656
|
+
* knowing the nonce race for one spend.
|
|
657
|
+
*
|
|
658
|
+
* @param {Note} note - The note to nullify.
|
|
659
|
+
* @return {Bytes<32>} - The nullifier published in `_nullifiers` when the
|
|
660
|
+
* note is spent.
|
|
661
|
+
*/
|
|
662
|
+
export pure circuit nullifierOf(note: Note): Bytes<32> {
|
|
663
|
+
return persistentHash<NullifierPreimage>(NullifierPreimage {
|
|
664
|
+
domain: pad(32, "CNFT:nullifier"),
|
|
665
|
+
nonce: note.nonce
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* @description Spends the caller's input note and creates a recipient note
|
|
671
|
+
* of `value` plus a change note back to the caller, conserving value.
|
|
672
|
+
*
|
|
673
|
+
* @constraints k=16, rows=55048
|
|
674
|
+
*
|
|
675
|
+
* @notice Local private result:
|
|
676
|
+
* Both notes return to the caller, who hands the recipient note over out of
|
|
677
|
+
* band and keeps the change.
|
|
678
|
+
*
|
|
679
|
+
* Requirements:
|
|
680
|
+
*
|
|
681
|
+
* - The input note is committed in the tree and unspent.
|
|
682
|
+
* - The caller owns the input note (its commitment binds `_spenderPk()`).
|
|
683
|
+
* - `value <= input.value`.
|
|
684
|
+
*
|
|
685
|
+
* @param {Field} recipientPk - The identity receiving the output note.
|
|
686
|
+
* @param {Uint<128>} value - The amount to send.
|
|
687
|
+
* @return {[Note, Note]} - The recipient's note and the caller's change note.
|
|
688
|
+
*/
|
|
689
|
+
export circuit transfer(recipientPk: Field, value: Uint<128>): [Note, Note] {
|
|
690
|
+
const pk = _spenderPk();
|
|
691
|
+
const input = _inputNote();
|
|
692
|
+
assert(input.value >= value, "ConfidentialNoteFungibleToken: insufficient note value");
|
|
693
|
+
|
|
694
|
+
// Peeked, not consumed: `_transfer` is what publishes the nullifier.
|
|
695
|
+
const spendId = degradeToTransient(nullifierOf(input));
|
|
696
|
+
const outNote = Note {
|
|
697
|
+
value: value,
|
|
698
|
+
nonce: freshNonce(pad(32, "CNFT:out"), spendId)
|
|
699
|
+
};
|
|
700
|
+
const changeNote = Note {
|
|
701
|
+
value: (input.value - value) as Uint<128>,
|
|
702
|
+
nonce: freshNonce(pad(32, "CNFT:change"), spendId)
|
|
703
|
+
};
|
|
704
|
+
_transfer(pk, recipientPk, outNote, changeNote);
|
|
705
|
+
return [disclose(outNote), disclose(changeNote)];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* @description Consumes the input note owned by `spenderPk` and commits
|
|
710
|
+
* `outNote` to `recipientPk` plus `changeNote` back, conserving value.
|
|
711
|
+
*
|
|
712
|
+
* @constraints k=16, rows=33905
|
|
713
|
+
*
|
|
714
|
+
* @notice Caller-built outputs:
|
|
715
|
+
* Taking both notes as parameters lets a composer run its own emission
|
|
716
|
+
* policy while the core still enforces conservation. Output nonces must be
|
|
717
|
+
* unpredictable; see `_mintNote`.
|
|
718
|
+
*
|
|
719
|
+
* @warning No authorization:
|
|
720
|
+
* The composer gates who may call this.
|
|
721
|
+
*
|
|
722
|
+
* Requirements:
|
|
723
|
+
*
|
|
724
|
+
* - The input note is committed in the tree and unspent.
|
|
725
|
+
* - The input note's commitment binds `spenderPk`.
|
|
726
|
+
* - `input.value == outNote.value + changeNote.value`.
|
|
727
|
+
*
|
|
728
|
+
* @param {Field} spenderPk - The identity that owns the input note.
|
|
729
|
+
* @param {Field} recipientPk - The identity receiving `outNote`.
|
|
730
|
+
* @param {Note} outNote - The recipient's output note.
|
|
731
|
+
* @param {Note} changeNote - The spender's change note.
|
|
732
|
+
* @return {[]} - Empty tuple.
|
|
733
|
+
*/
|
|
734
|
+
export circuit _transfer(spenderPk: Field, recipientPk: Field, outNote: Note, changeNote: Note): [] {
|
|
735
|
+
const input = _consumeNote(spenderPk);
|
|
736
|
+
assert(input.value == outNote.value + changeNote.value,
|
|
737
|
+
"ConfidentialNoteFungibleToken: transfer does not conserve value");
|
|
738
|
+
_mintNote(outNote, recipientPk);
|
|
739
|
+
_mintNote(changeNote, spenderPk);
|
|
740
|
+
}
|
|
741
|
+
}
|