@openzeppelin/compact-contracts 0.5.0-alpha.1 → 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 +19 -20
- package/access/ShieldedAccessControl.compact +1 -1
- package/access/ZOwnablePK.compact +3 -3
- package/crypto/Ecdh.compact +1 -1
- package/crypto/EcdhMask.compact +61 -22
- 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 +1 -1
- package/multisig/ForwarderShielded.compact +1 -1
- package/multisig/ForwarderUnshielded.compact +1 -1
- package/multisig/ProposalManager.compact +3 -3
- package/multisig/Secp256k1EcdsaMultisig.compact +1 -1
- package/multisig/ShieldedTreasury.compact +1 -1
- package/multisig/ShieldedTreasuryStateless.compact +5 -11
- package/multisig/Signer.compact +1 -1
- package/multisig/UnshieldedTreasury.compact +1 -1
- package/multisig/examples/ForwarderPrivateExample.compact +1 -1
- 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 +1 -1
- package/package.json +11 -11
- package/security/Allowlist.compact +1 -1
- package/security/Blocklist.compact +1 -1
- package/security/Initializable.compact +1 -1
- package/security/Pausable.compact +1 -1
- package/token/ConfidentialFungibleToken.compact +1 -1
- package/token/ConfidentialNoteFungibleToken.compact +1 -1
- package/token/FungibleToken.compact +34 -30
- package/token/MultiToken.compact +25 -21
- package/token/NativeShieldedToken.compact +2 -2
- package/token/NativeShieldedTokenCore.compact +1 -1
- package/token/NativeShieldedTokenFamily.compact +4 -4
- package/token/NonFungibleToken.compact +26 -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/presets/ShieldedMultiSigV2.compact +0 -289
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/extensions/ConfidentialNoteFungibleTokenDelivery.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.26.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module ConfidentialNoteFungibleTokenDelivery
|
|
8
|
+
* @description Extension that publishes every output note
|
|
9
|
+
* of a `ConfidentialNoteFungibleToken` encrypted to its owner's Jubjub key, so
|
|
10
|
+
* a wallet discovers its notes from chain data alone. One ECDH per output, two
|
|
11
|
+
* one-time pads from `EcdhMask.kdf`, one append-only `_deliveries` list. It
|
|
12
|
+
* imports no token module: the composer calls `_deliver` beside the core's
|
|
13
|
+
* note creation.
|
|
14
|
+
*
|
|
15
|
+
* @dev Notation. `Hf`, `pk`, `nonce` and `slot` are defined in the core
|
|
16
|
+
* - `encPk = g^ekScalar`: the owner's Jubjub encryption key, paired with the
|
|
17
|
+
* spend identity `pk` out of band.
|
|
18
|
+
* - `e = Hf("CNFT:delivery:e", seed, slot, nonce, encPk)`: the ephemeral
|
|
19
|
+
* scalar.
|
|
20
|
+
* - `E = g^e` and `S = encPk^e = E^ekScalar`: the `crypto/Ecdh` exchange.
|
|
21
|
+
* - `valueCt = (value + kdf(S, "CNFT:delivery:value")) mod 2^248` and
|
|
22
|
+
* `nonceCt = (nonce + kdf(S, "CNFT:delivery:nonce")) mod 2^248`: one-time
|
|
23
|
+
* pads over `[0, 2^248)`, the width of `kdf`'s output.
|
|
24
|
+
*
|
|
25
|
+
* @dev Compatibility:
|
|
26
|
+
* The wire format a wallet depends on is the `Delivery` layout, the two pad
|
|
27
|
+
* tags, and the modular pad. The `e` tag and the `EphemeralPreimage` layout
|
|
28
|
+
* are sender-side: a recipient recovers `S` from `E` and never recomputes `e`.
|
|
29
|
+
* The compatibility suite pins all of them.
|
|
30
|
+
*
|
|
31
|
+
* @notice Privacy:
|
|
32
|
+
* - Public, per output: `E`, a fresh pseudorandom point, and two pad outputs,
|
|
33
|
+
* each uniform to anyone without `ekScalar`. No field names `pk`, `encPk`
|
|
34
|
+
* or `cm`, so entries cannot be grouped by recipient.
|
|
35
|
+
* - Public, per tx: one entry per `_deliver` call, beside the leaf it
|
|
36
|
+
* describes. The count is constant per entry point when the composer
|
|
37
|
+
* delivers every output (see Composition).
|
|
38
|
+
* - Private: `encPk`, `value`, `nonce`, the seed, `e`, `S`. `slot` is hashed
|
|
39
|
+
* into `e` and never written; it is a public constant of the composer.
|
|
40
|
+
* - Hiding: with a uniform mask the pad is perfect, so `valueCt` and
|
|
41
|
+
* `nonceCt` carry nothing about the plaintext, regardless of its width
|
|
42
|
+
* or distribution; the only bound is `2^248` (`UintN<248>.MODULUS`). The mask's
|
|
43
|
+
* uniformity is `EcdhMask`'s assumption: hashed ElGamal, CDH in the
|
|
44
|
+
* random-oracle model.
|
|
45
|
+
*
|
|
46
|
+
* @notice Security:
|
|
47
|
+
* - The nonce is the spend capability and the pads are one-time, so a
|
|
48
|
+
* repeated `e` to one key opens both notes. `e` binds the nonce and the
|
|
49
|
+
* key: two committed notes never share a pad even under a reused seed,
|
|
50
|
+
* since `_issuedNonces` keeps nonces unique, and one note to two keys
|
|
51
|
+
* never shares `E`. The seed MUST still be fresh and secret per call. `e`
|
|
52
|
+
* is secret only while the seed or the nonce is, and a known `e` opens the
|
|
53
|
+
* entry to everyone.
|
|
54
|
+
* - No authentication: the pads are malleable and nothing binds an entry to a
|
|
55
|
+
* leaf. A wallet accepts a recovered note only once `commitOf(note, pk)` is
|
|
56
|
+
* in the tree (see Client-side duties).
|
|
57
|
+
* - No authorization: `_deliver` is ungated like the core's `_` circuits. The
|
|
58
|
+
* composer's gate on note creation is the gate on delivery.
|
|
59
|
+
* - Not checked here: that the delivered pair is the committed note, and that
|
|
60
|
+
* `encPk` pairs with the owner's `pk`. The composer passes one note to
|
|
61
|
+
* `_mintNote` and to `_deliver` in one circuit. A wrong `encPk` makes the
|
|
62
|
+
* note undiscoverable, not stealable, since `cm` binds the owner's `pk`.
|
|
63
|
+
*
|
|
64
|
+
* @notice Concurrency:
|
|
65
|
+
* `_deliver` reads nothing and pins nothing, so every delivery commutes with
|
|
66
|
+
* every other call.
|
|
67
|
+
*
|
|
68
|
+
* @notice Composition:
|
|
69
|
+
* - Call `_deliver` once per output and per intended key, zero-value change
|
|
70
|
+
* included, never under a condition on private data: the entry count per
|
|
71
|
+
* entry point must stay constant.
|
|
72
|
+
* - `slot` separates repeated deliveries of one note to one key. Under a
|
|
73
|
+
* reused seed, the same note to the same key with one slot repeats the
|
|
74
|
+
* entry byte for byte; distinct slots give each delivery its own `E`.
|
|
75
|
+
* - Nothing to initialize and no ledger read: `_deliver` runs from the
|
|
76
|
+
* composer's constructor, which is how a constructor-time mint reaches its
|
|
77
|
+
* owner.
|
|
78
|
+
* - Delivering a note twice adds an entry and nothing else. Wallets dedupe by
|
|
79
|
+
* commitment.
|
|
80
|
+
* - A composed contract supersedes the core header's out-of-band delivery and
|
|
81
|
+
* no-rescan statements: its wallets find their notes by scanning
|
|
82
|
+
* `_deliveries`.
|
|
83
|
+
*
|
|
84
|
+
* @notice Client-side duties:
|
|
85
|
+
* - Keys: `encPk = g^ekScalar`. Derive `ekScalar` from the wallet seed under a
|
|
86
|
+
* module-specific domain (`ElGamal.secretToScalar` is the library's
|
|
87
|
+
* derivation) and share `(pk, encPk)` together.
|
|
88
|
+
* - Scan: read `_deliveries` from the length last seen, newest first. For each
|
|
89
|
+
* entry, `recoverNote(entry, ekScalar)`; drop `value >= 2^128`, a wrong key
|
|
90
|
+
* with overwhelming probability; keep the note iff
|
|
91
|
+
* `commitOf({ value, nonce }, pk)` is a leaf, and record the leaf index;
|
|
92
|
+
* `nullifierOf(note)` in `_nullifiers` means spent. One scalar
|
|
93
|
+
* multiplication per entry, no proof.
|
|
94
|
+
* - `ekScalar` never enters an on-chain circuit: `recoverNote` is pure and
|
|
95
|
+
* runs in the wallet.
|
|
96
|
+
*
|
|
97
|
+
* @notice Scalability:
|
|
98
|
+
* `_deliveries` gains one entry per output and is never pruned; every wallet
|
|
99
|
+
* scans every entry once.
|
|
100
|
+
*
|
|
101
|
+
* @dev Performance:
|
|
102
|
+
* `_deliver` pays one SHA-256 over the ephemeral preimage, two scalar
|
|
103
|
+
* multiplications, one point digest, two masks derived from it and the two
|
|
104
|
+
* modular pads.
|
|
105
|
+
*/
|
|
106
|
+
module ConfidentialNoteFungibleTokenDelivery {
|
|
107
|
+
import CompactStandardLibrary;
|
|
108
|
+
import "../../crypto/Ecdh" prefix Ecdh_;
|
|
109
|
+
import "../../crypto/EcdhMask" prefix EcdhMask_;
|
|
110
|
+
import { wrappingAdd, wrappingSub } from "../../math/UintN"<248> prefix U248_;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @description One output note, encrypted to its owner. Public once written;
|
|
114
|
+
* no field is secret on its own.
|
|
115
|
+
* @type {JubjubPoint} ephemeralPk - `E = g^e`, fresh per output. Never the
|
|
116
|
+
* identity.
|
|
117
|
+
* @type {Uint<248>} valueCt - `(value + kdf(S, "CNFT:delivery:value")) mod 2^248`.
|
|
118
|
+
* @type {Uint<248>} nonceCt - `(nonce + kdf(S, "CNFT:delivery:nonce")) mod 2^248`.
|
|
119
|
+
*
|
|
120
|
+
* @dev The `Uint<248>` pads keep both ciphertext atoms below 32 bytes. The
|
|
121
|
+
* generated `List` accessors (`head`, `popFront`) throw `exceeded structure
|
|
122
|
+
* bounds` on an entry with a full-width field atom.
|
|
123
|
+
*/
|
|
124
|
+
export struct Delivery {
|
|
125
|
+
ephemeralPk: JubjubPoint;
|
|
126
|
+
valueCt: Uint<248>;
|
|
127
|
+
nonceCt: Uint<248>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @description An entry with its pads stripped, nothing checked. A wrong key
|
|
132
|
+
* yields a uniform pair; the wallet range-checks `value` and matches the
|
|
133
|
+
* commitment.
|
|
134
|
+
* @type {Uint<248>} value - The note's value, below `2^128` for a note
|
|
135
|
+
* addressed to the key.
|
|
136
|
+
* @type {Uint<248>} nonce - The note's nonce.
|
|
137
|
+
*/
|
|
138
|
+
export struct RecoveredNote {
|
|
139
|
+
value: Uint<248>;
|
|
140
|
+
nonce: Uint<248>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @description Preimage of the ephemeral scalar: the core's `NoncePreimage`
|
|
145
|
+
* shape plus the recipient key. The domain tag and the extra field keep `e`
|
|
146
|
+
* apart from a core nonce drawn from the same seed.
|
|
147
|
+
* @type {Bytes<32>} domain - The ephemeral domain tag.
|
|
148
|
+
* @type {Bytes<32>} seed - The caller's per-invocation randomness.
|
|
149
|
+
* @type {Bytes<32>} slot - The per-output tag.
|
|
150
|
+
* @type {Field} nonce - The delivered note's nonce.
|
|
151
|
+
* @type {JubjubPoint} encPk - The key the entry is addressed to.
|
|
152
|
+
*/
|
|
153
|
+
struct EphemeralPreimage {
|
|
154
|
+
domain: Bytes<32>;
|
|
155
|
+
seed: Bytes<32>;
|
|
156
|
+
slot: Bytes<32>;
|
|
157
|
+
nonce: Field;
|
|
158
|
+
encPk: JubjubPoint;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @description Every delivery ever made, newest first. Append-only: no
|
|
163
|
+
* circuit reads, removes or overwrites an entry. Unbounded.
|
|
164
|
+
* @type {List<Delivery>} _deliveries - The feed wallets scan.
|
|
165
|
+
*/
|
|
166
|
+
export ledger _deliveries: List<Delivery>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @witness wit_ConfidentialNoteDeliveryRandomness
|
|
170
|
+
* @description Returns the seed `_deliver` derives its ephemeral scalar
|
|
171
|
+
* from.
|
|
172
|
+
*
|
|
173
|
+
* @dev Freshness is mandatory:
|
|
174
|
+
* MUST be fresh and secret per call. The nonce binding turns a reused seed
|
|
175
|
+
* from a stolen note into nothing; `e` still stays secret only while the
|
|
176
|
+
* seed or the nonce does.
|
|
177
|
+
*
|
|
178
|
+
* @returns {Bytes<32>} seed - A 32-byte cryptographically secure random
|
|
179
|
+
* value.
|
|
180
|
+
*/
|
|
181
|
+
witness wit_ConfidentialNoteDeliveryRandomness(): Bytes<32>;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @description Publishes `(value, nonce)` encrypted to `encPk`: one ECDH
|
|
185
|
+
* under a key- and nonce-bound ephemeral, two modular `EcdhMask` pads, one
|
|
186
|
+
* `_deliveries` entry. Call it beside the core's note creation, once per
|
|
187
|
+
* output and intended key.
|
|
188
|
+
*
|
|
189
|
+
* @constraints k=15, rows=24782
|
|
190
|
+
*
|
|
191
|
+
* @warning No authorization:
|
|
192
|
+
* The composer gates who may call this.
|
|
193
|
+
*
|
|
194
|
+
* @dev Key- and nonce-bound ephemeral:
|
|
195
|
+
* `e = Hf(domain, seed, slot, nonce, encPk)`, so `e` repeats only when all
|
|
196
|
+
* of `(seed, slot, nonce, encPk)` repeat: a reused seed never pads two
|
|
197
|
+
* committed notes alike and never gives two keys one `E`.
|
|
198
|
+
*
|
|
199
|
+
* @dev Constant shape:
|
|
200
|
+
* No branch on the ledger and no ledger read: the transcript is independent
|
|
201
|
+
* of every input, and two deliveries in one block never conflict.
|
|
202
|
+
*
|
|
203
|
+
* Requirements:
|
|
204
|
+
*
|
|
205
|
+
* - `encPk` is not the identity point.
|
|
206
|
+
* - `nonce < 2^248`. Every `degradeToTransient` output qualifies.
|
|
207
|
+
*
|
|
208
|
+
* @param {JubjubPoint} encPk - The owner's encryption key, `g^ekScalar`.
|
|
209
|
+
* @param {Uint<128>} value - The note's value.
|
|
210
|
+
* @param {Field} nonce - The note's nonce.
|
|
211
|
+
* @param {Bytes<32>} slot - The per-output tag; distinct per delivery when
|
|
212
|
+
* one note goes to one key more than once.
|
|
213
|
+
* @return {[]} - Empty tuple.
|
|
214
|
+
*/
|
|
215
|
+
export circuit _deliver(encPk: JubjubPoint, value: Uint<128>, nonce: Field, slot: Bytes<32>): [] {
|
|
216
|
+
const e = degradeToTransient(persistentHash<EphemeralPreimage>(EphemeralPreimage {
|
|
217
|
+
seed: wit_ConfidentialNoteDeliveryRandomness(),
|
|
218
|
+
domain: pad(32, "CNFT:delivery:e"),
|
|
219
|
+
slot: slot,
|
|
220
|
+
nonce: nonce,
|
|
221
|
+
encPk: encPk
|
|
222
|
+
})) as JubjubScalar;
|
|
223
|
+
// The identity-key and zero-ephemeral guards come from `deriveShared`.
|
|
224
|
+
const shared = Ecdh_deriveShared(encPk, e);
|
|
225
|
+
// One point digest feeds both pads.
|
|
226
|
+
const sharedDigest = EcdhMask_pointDigest(shared.sShared);
|
|
227
|
+
// The cast is the width check: a bit above the pad would sit in the clear.
|
|
228
|
+
const paddedNonce = nonce as Uint<248>;
|
|
229
|
+
const delivery = Delivery {
|
|
230
|
+
ephemeralPk: shared.ephemeralPk,
|
|
231
|
+
valueCt: U248_wrappingAdd(value as Uint<248>,
|
|
232
|
+
EcdhMask_kdfFromDigest(sharedDigest, pad(32, "CNFT:delivery:value")) as Uint<248>),
|
|
233
|
+
nonceCt: U248_wrappingAdd(paddedNonce,
|
|
234
|
+
EcdhMask_kdfFromDigest(sharedDigest, pad(32, "CNFT:delivery:nonce")) as Uint<248>)
|
|
235
|
+
};
|
|
236
|
+
_deliveries.pushFront(disclose(delivery));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @description Strips the pads from an entry with the owner's secret scalar.
|
|
241
|
+
* Pure and off-chain: a wallet reads its notes with no proof.
|
|
242
|
+
*
|
|
243
|
+
* @constraints k=14, rows=15063
|
|
244
|
+
*
|
|
245
|
+
* @notice Raw result:
|
|
246
|
+
* Nothing derived from `ekScalar` or the recovered pair is checked, so the
|
|
247
|
+
* circuit is no oracle for a candidate key. A wrong key returns a uniform
|
|
248
|
+
* pair; the wallet filters on `value < 2^128` and the commitment tree.
|
|
249
|
+
*
|
|
250
|
+
* @dev Identity ephemeral:
|
|
251
|
+
* `_deliver` never writes one, and its pads would be public constants, so
|
|
252
|
+
* the entry is refused rather than opened, as `EcdhMask.decrypt` does.
|
|
253
|
+
*
|
|
254
|
+
* Requirements:
|
|
255
|
+
*
|
|
256
|
+
* - `delivery.ephemeralPk` is not the identity point.
|
|
257
|
+
*
|
|
258
|
+
* @param {Delivery} delivery - The entry to open.
|
|
259
|
+
* @param {JubjubScalar} ekScalar - The secret behind the `encPk` the entry
|
|
260
|
+
* was addressed to.
|
|
261
|
+
* @return {RecoveredNote} - The padded pair.
|
|
262
|
+
*/
|
|
263
|
+
export pure circuit recoverNote(delivery: Delivery, ekScalar: JubjubScalar): RecoveredNote {
|
|
264
|
+
assert(delivery.ephemeralPk != ecMulGenerator(0 as JubjubScalar),
|
|
265
|
+
"ConfidentialNoteFungibleTokenDelivery: identity ephemeral");
|
|
266
|
+
const sharedDigest = EcdhMask_pointDigest(Ecdh_recoverShared(delivery.ephemeralPk, ekScalar));
|
|
267
|
+
return RecoveredNote {
|
|
268
|
+
value: U248_wrappingSub(delivery.valueCt,
|
|
269
|
+
EcdhMask_kdfFromDigest(sharedDigest, pad(32, "CNFT:delivery:value")) as Uint<248>),
|
|
270
|
+
nonce: U248_wrappingSub(delivery.nonceCt,
|
|
271
|
+
EcdhMask_kdfFromDigest(sharedDigest, pad(32, "CNFT:delivery:nonce")) as Uint<248>)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.5.0-alpha.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/extensions/NativeShieldedTokenDerivedNonce.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/extensions/NativeShieldedTokenFamilyPublicSupply.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/extensions/NativeShieldedTokenPublicSupply.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/extensions/NativeShieldedTokenPublicSupplyCore.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (token/presets/ZOwnableNativeShieldedToken.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
package/utils/EvmAbi.compact
CHANGED
package/utils/Utils.compact
CHANGED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.5.0-alpha.1 (multisig/presets/ShieldedMultiSigV2.compact)
|
|
3
|
-
|
|
4
|
-
pragma language_version >= 0.26.0;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @module ShieldedMultiSigV2
|
|
8
|
-
* @description Privacy-preserving threshold multisig contract.
|
|
9
|
-
*
|
|
10
|
-
* Signer identities are stored as commitments: hashes of ECDSA public
|
|
11
|
-
* keys combined with an instance salt and domain separator. Signature
|
|
12
|
-
* verification happens in a single transaction with no multi-step
|
|
13
|
-
* proposal lifecycle. The contract enforces threshold authorization
|
|
14
|
-
* and replay protection. All other coordination (signature collection,
|
|
15
|
-
* coin selection) happens off-chain.
|
|
16
|
-
*
|
|
17
|
-
* `t` is the approval width and the threshold: `execute` verifies exactly
|
|
18
|
-
* `t` distinct signers. Import as `"../presets/ShieldedMultiSigV2"<t>`; the
|
|
19
|
-
* implementing contract fixes the signer count `n` at `initialize`. Import
|
|
20
|
-
* one width per contract: two widths share one signer registry, see
|
|
21
|
-
* `Secp256k1EcdsaMultisig`.
|
|
22
|
-
*
|
|
23
|
-
* Treasury is fully stateless meaning coin data is not stored on the public ledger.
|
|
24
|
-
* Deposits call receiveShielded only. The operator discovers coin indices
|
|
25
|
-
* through ZswapOutput events from the indexer, constructs QualifiedShieldedCoinInfo
|
|
26
|
-
* off-chain, and provides it as a circuit parameter for spending.
|
|
27
|
-
*
|
|
28
|
-
* @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
|
|
29
|
-
*
|
|
30
|
-
* @notice Implementing contracts register the signer set from their
|
|
31
|
-
* constructor by calling this module's `initialize`, and MUST NOT import
|
|
32
|
-
* `Signer` or `Secp256k1EcdsaMultisig` directly.
|
|
33
|
-
*/
|
|
34
|
-
module ShieldedMultiSigV2<#t> {
|
|
35
|
-
import CompactStandardLibrary;
|
|
36
|
-
|
|
37
|
-
import "../Secp256k1EcdsaMultisig"<t> prefix Secp256k1EcdsaMultisig_;
|
|
38
|
-
import "../../crypto/Eip712" prefix Eip712_;
|
|
39
|
-
import "../../utils/EvmAbi" prefix EvmAbi_;
|
|
40
|
-
import "../ProposalManager" prefix Proposal_;
|
|
41
|
-
import "../ShieldedTreasuryStateless" prefix Treasury_;
|
|
42
|
-
|
|
43
|
-
// Implementing contracts need this for the re-exported `execute` signature.
|
|
44
|
-
// Importing `ProposalManager` again instead would give them a second,
|
|
45
|
-
// unwritten copy of its state.
|
|
46
|
-
export { Proposal_Recipient };
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @description Monotonic operation counter. `execute` reads it as that
|
|
50
|
-
* operation's nonce and then increments, so no two executions share one.
|
|
51
|
-
*/
|
|
52
|
-
ledger _nonce: Counter;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @description The EIP-712 domain separator every `execute` digest is built
|
|
56
|
-
* under, derived from `(name, version, instanceSalt)`. Set once at
|
|
57
|
-
* construction.
|
|
58
|
-
*/
|
|
59
|
-
export sealed ledger _domainSeparator: Bytes<32>;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* @description Registers the signer set at threshold `t` and fixes the
|
|
63
|
-
* EIP-712 domain separator.
|
|
64
|
-
*
|
|
65
|
-
* @notice Call from the implementing contract's constructor only. It writes
|
|
66
|
-
* the `sealed` `_domainSeparator`, so implementing contracts MUST NOT expose
|
|
67
|
-
* `initialize` as a post-deploy circuit.
|
|
68
|
-
*
|
|
69
|
-
* The EIP-712 domain fixed here is `(name, version, salt)`. `chainId` and
|
|
70
|
-
* `verifyingContract` are absent: no network identifier is available
|
|
71
|
-
* in-circuit, and a 32-byte Midnight address does not fit
|
|
72
|
-
* `verifyingContract`'s `address` type.
|
|
73
|
-
*
|
|
74
|
-
* Replay protection comes from `execute` binding `kernel.self()` in its
|
|
75
|
-
* struct rather than from the domain. The ledger randomizes a contract's
|
|
76
|
-
* address at deployment, so no two deployments share one on any network.
|
|
77
|
-
* A signature for one cannot authorize another.
|
|
78
|
-
*
|
|
79
|
-
* `instanceSalt` also keys the signer commitments, so a fresh value per
|
|
80
|
-
* deployment keeps one public key from producing the same commitment in two
|
|
81
|
-
* deployments which would link the signer set across them.
|
|
82
|
-
*
|
|
83
|
-
* Requirements:
|
|
84
|
-
*
|
|
85
|
-
* - `instanceSalt` should be cryptographically random.
|
|
86
|
-
* - `signerCommitments` must not contain duplicates.
|
|
87
|
-
* - `n` must be at least `t`.
|
|
88
|
-
*
|
|
89
|
-
* @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
|
|
90
|
-
* @param {Vector<n, Bytes<32>>} signerCommitments - Hashed signer identities.
|
|
91
|
-
* @returns {[]} Empty tuple.
|
|
92
|
-
*/
|
|
93
|
-
export circuit initialize<#n>(
|
|
94
|
-
instanceSalt: Bytes<32>,
|
|
95
|
-
signerCommitments: Vector<n, Bytes<32>>
|
|
96
|
-
): [] {
|
|
97
|
-
Secp256k1EcdsaMultisig_initialize<n>(instanceSalt, signerCommitments);
|
|
98
|
-
|
|
99
|
-
const hashedName = keccak256<Bytes<18>>(pad(18, "ShieldedMultiSigV2"));
|
|
100
|
-
const hashedVersion = keccak256<Bytes<1>>(pad(1, "1"));
|
|
101
|
-
_domainSeparator = Eip712_domainSeparator(
|
|
102
|
-
hashedName,
|
|
103
|
-
hashedVersion,
|
|
104
|
-
disclose(instanceSalt)
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @description `keccak256` of the `Execute` type string:
|
|
110
|
-
* `Execute(bytes32 contractAddress,uint256 nonce,uint8 recipientKind,bytes32 recipient,bytes32 coinColor,uint256 amount)`
|
|
111
|
-
*
|
|
112
|
-
* @dev Written as a literal word. `compactc` emits a full keccak
|
|
113
|
-
* permutation even for a constant argument, so hashing the type string
|
|
114
|
-
* in-circuit would add ~4.2k rows to every `execute`. The spec checks this
|
|
115
|
-
* word against the type string above.
|
|
116
|
-
*
|
|
117
|
-
* @returns {Bytes<32>} The `Execute` type hash.
|
|
118
|
-
*/
|
|
119
|
-
pure circuit EXECUTE_TYPEHASH(): Bytes<32> {
|
|
120
|
-
const typeHash: Vector<32, Uint<8>> = [
|
|
121
|
-
0x8e, 0x36, 0xc9, 0x78, 0xf2, 0x00, 0x15, 0xe8,
|
|
122
|
-
0x1d, 0x7a, 0x53, 0xef, 0xc3, 0x2e, 0x2b, 0x08,
|
|
123
|
-
0xf1, 0x24, 0x97, 0xa5, 0x05, 0x7c, 0x70, 0x20,
|
|
124
|
-
0x7f, 0x8b, 0xc4, 0xb7, 0xc6, 0xcf, 0x5e, 0x15
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
return typeHash as Bytes<32>;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @description Receives a shielded coin into the multisig treasury.
|
|
132
|
-
*
|
|
133
|
-
* No access control which allows anyone to deposit. The coin is claimed at the
|
|
134
|
-
* protocol level through receiveShielded. No coin data is stored on the
|
|
135
|
-
* public ledger, preserving full balance privacy.
|
|
136
|
-
*
|
|
137
|
-
* The operator discovers the coin's Merkle tree index by subscribing
|
|
138
|
-
* to ZswapOutput events via the indexer, filtering by contract address,
|
|
139
|
-
* and extracting mt_index. Combined with the known ShieldedCoinInfo,
|
|
140
|
-
* this produces the QualifiedShieldedCoinInfo needed for spending.
|
|
141
|
-
*
|
|
142
|
-
* @constraints k=13, rows=5898 (zkir-v3)
|
|
143
|
-
*
|
|
144
|
-
* @param {ShieldedCoinInfo} coin - The incoming shielded coin.
|
|
145
|
-
*/
|
|
146
|
-
export circuit deposit(coin: ShieldedCoinInfo): [] {
|
|
147
|
-
receiveShielded(disclose(coin));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @description Executes a shielded send authorized by threshold signatures.
|
|
152
|
-
*
|
|
153
|
-
* The circuit reads the current nonce from the ledger, increments it,
|
|
154
|
-
* then reconstructs the EIP-712 digest that signers must have signed
|
|
155
|
-
* off-chain: the `Execute` struct (contract address, nonce, recipient kind,
|
|
156
|
-
* recipient, coin color, amount) hashed under this deployment's domain
|
|
157
|
-
* separator. The digest is bound to the contract instance and the full recipient (kind and
|
|
158
|
-
* address), so a signature authorizes exactly one operation on one instance.
|
|
159
|
-
*
|
|
160
|
-
* Approvals are checked by `Secp256k1EcdsaMultisig`, which derives each
|
|
161
|
-
* signer's commitment from their public key and the instance salt and
|
|
162
|
-
* rejects duplicates. Constraints below are measured at `t = 2`.
|
|
163
|
-
*
|
|
164
|
-
* @notice `to` must be a `ShieldedUser`. A contract-addressed coin would be
|
|
165
|
-
* unclaimable, making the transaction malformed.
|
|
166
|
-
*
|
|
167
|
-
* @constraints k=17, rows=98969 (zkir-v3)
|
|
168
|
-
*
|
|
169
|
-
* Requirements:
|
|
170
|
-
*
|
|
171
|
-
* - No two entries may be the same signer.
|
|
172
|
-
* - Every public key must hash to a registered signer commitment.
|
|
173
|
-
* - Every signature must be valid over the message hash and use the low-s
|
|
174
|
-
* encoding.
|
|
175
|
-
* - `to.kind` must be `ShieldedUser`.
|
|
176
|
-
* - Coin value must be >= amount.
|
|
177
|
-
*
|
|
178
|
-
* @param {Proposal_Recipient} to - The recipient.
|
|
179
|
-
* @param {Uint<128>} amount - The amount to send.
|
|
180
|
-
* @param {QualifiedShieldedCoinInfo} coin - The coin to spend (from operator's pool).
|
|
181
|
-
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
182
|
-
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the operation.
|
|
183
|
-
*
|
|
184
|
-
* @returns {ShieldedSendResult} The send result including any change.
|
|
185
|
-
*/
|
|
186
|
-
export circuit execute(
|
|
187
|
-
to: Proposal_Recipient,
|
|
188
|
-
amount: Uint<128>,
|
|
189
|
-
coin: QualifiedShieldedCoinInfo,
|
|
190
|
-
pubkeys: Vector<t, Secp256k1Point>,
|
|
191
|
-
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
192
|
-
): ShieldedSendResult {
|
|
193
|
-
// Increment nonce
|
|
194
|
-
const currentNonce = _nonce;
|
|
195
|
-
_nonce.increment(1);
|
|
196
|
-
|
|
197
|
-
// Construct message hash
|
|
198
|
-
const msgHash = Eip712_hashTypedData(_domainSeparator, keccak256<Vector<7, Bytes<32>>>([
|
|
199
|
-
EXECUTE_TYPEHASH(),
|
|
200
|
-
// Binds the digest to this deployment. The domain cannot carry the
|
|
201
|
-
// address, so removing this word removes cross-instance replay
|
|
202
|
-
// protection.
|
|
203
|
-
kernel.self().bytes,
|
|
204
|
-
EvmAbi_uint64Word(currentNonce),
|
|
205
|
-
EvmAbi_uint8Word(to.kind as Uint<8>),
|
|
206
|
-
to.address,
|
|
207
|
-
coin.color,
|
|
208
|
-
EvmAbi_uint128Word(amount)
|
|
209
|
-
]));
|
|
210
|
-
|
|
211
|
-
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
212
|
-
|
|
213
|
-
assert(
|
|
214
|
-
to.kind == Proposal_RecipientKind.ShieldedUser,
|
|
215
|
-
"ShieldedMultiSigV2: recipient must be a coin public key"
|
|
216
|
-
);
|
|
217
|
-
const recipient = left<ZswapCoinPublicKey, ContractAddress>(
|
|
218
|
-
ZswapCoinPublicKey { bytes: to.address }
|
|
219
|
-
);
|
|
220
|
-
return Treasury__send(coin, recipient, amount);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* @description Computes a signer commitment from a secp256k1 public key,
|
|
225
|
-
* as `persistentHash(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`.
|
|
226
|
-
*
|
|
227
|
-
* Pure, so the deployer can derive constructor commitments off-chain.
|
|
228
|
-
*
|
|
229
|
-
* Requirements:
|
|
230
|
-
*
|
|
231
|
-
* - `pk` must not be the secp256k1 identity point, which has no coordinates.
|
|
232
|
-
*
|
|
233
|
-
* @param {Secp256k1Point} pk - The secp256k1 public key.
|
|
234
|
-
* @param {Bytes<32>} salt - The instance salt.
|
|
235
|
-
*
|
|
236
|
-
* @returns {Bytes<32>} The signer commitment.
|
|
237
|
-
*/
|
|
238
|
-
export pure circuit _calculateSignerId(
|
|
239
|
-
pk: Secp256k1Point,
|
|
240
|
-
salt: Bytes<32>
|
|
241
|
-
): Bytes<32> {
|
|
242
|
-
return Secp256k1EcdsaMultisig_calculateSignerId(pk, salt);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* @description Returns the nonce the next `execute` digest will bind to.
|
|
247
|
-
*
|
|
248
|
-
* @constraints k=6, rows=34 (zkir-v3)
|
|
249
|
-
*
|
|
250
|
-
* @returns {Uint<64>} The current nonce.
|
|
251
|
-
*/
|
|
252
|
-
export circuit getNonce(): Uint<64> {
|
|
253
|
-
return _nonce;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* @description Returns the number of registered signers.
|
|
258
|
-
*
|
|
259
|
-
* @constraints k=6, rows=34 (zkir-v3)
|
|
260
|
-
*
|
|
261
|
-
* @returns {Uint<8>} The signer count.
|
|
262
|
-
*/
|
|
263
|
-
export circuit getSignerCount(): Uint<8> {
|
|
264
|
-
return Secp256k1EcdsaMultisig_getSignerCount();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* @description Returns the approval threshold.
|
|
269
|
-
*
|
|
270
|
-
* @constraints k=6, rows=34 (zkir-v3)
|
|
271
|
-
*
|
|
272
|
-
* @returns {Uint<8>} The threshold.
|
|
273
|
-
*/
|
|
274
|
-
export circuit getThreshold(): Uint<8> {
|
|
275
|
-
return Secp256k1EcdsaMultisig_getThreshold();
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* @description Returns whether the given commitment is a registered signer.
|
|
280
|
-
*
|
|
281
|
-
* @constraints k=8, rows=129 (zkir-v3)
|
|
282
|
-
*
|
|
283
|
-
* @param {Bytes<32>} commitment - The signer commitment to check.
|
|
284
|
-
* @returns {Boolean} True if registered.
|
|
285
|
-
*/
|
|
286
|
-
export circuit isSigner(commitment: Bytes<32>): Boolean {
|
|
287
|
-
return Secp256k1EcdsaMultisig_isSigner(commitment);
|
|
288
|
-
}
|
|
289
|
-
}
|