@openzeppelin/compact-contracts 0.4.0-alpha.5 → 0.5.0-alpha.1
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 +1 -1
- package/access/AccessControl.compact +1 -1
- package/access/Ownable.compact +1 -1
- package/access/ShieldedAccessControl.compact +30 -26
- package/access/ZOwnablePK.compact +13 -4
- package/crypto/Ecdh.compact +153 -0
- package/crypto/EcdhMask.compact +103 -70
- package/crypto/Ecdsa.compact +1 -1
- package/crypto/Eip712.compact +1 -1
- package/crypto/ElGamal.compact +1 -1
- package/multisig/ForwarderPrivate.compact +1 -1
- package/multisig/ForwarderShielded.compact +1 -1
- package/multisig/ForwarderUnshielded.compact +1 -1
- package/multisig/ProposalManager.compact +1 -1
- package/multisig/Secp256k1EcdsaMultisig.compact +275 -0
- package/multisig/ShieldedTreasury.compact +1 -1
- package/multisig/ShieldedTreasuryStateless.compact +1 -1
- 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/NativeShieldedTokenIssuerExample.compact +4 -4
- package/multisig/examples/ShieldedMultiSigV2Example.compact +6 -8
- package/multisig/presets/NativeShieldedTokenIssuer.compact +84 -83
- package/multisig/presets/ShieldedMultiSigV2.compact +35 -43
- package/package.json +1 -1
- 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 +741 -0
- package/token/FungibleToken.compact +1 -1
- package/token/MultiToken.compact +1 -1
- package/token/NativeShieldedToken.compact +1 -1
- package/token/NativeShieldedTokenCore.compact +1 -1
- package/token/NativeShieldedTokenFamily.compact +1 -1
- package/token/NonFungibleToken.compact +1 -1
- package/token/examples/ZOwnableNativeShieldedTokenExample.compact +109 -0
- package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
- package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
- package/token/presets/ZOwnableNativeShieldedToken.compact +314 -0
- package/utils/EvmAbi.compact +1 -1
- package/utils/Utils.compact +1 -1
- package/multisig/EcdsaSignerManager.compact +0 -248
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.1 (multisig/presets/NativeShieldedTokenIssuer.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module NativeShieldedTokenIssuer
|
|
8
8
|
* @description
|
|
9
|
-
* A
|
|
9
|
+
* A threshold ECDSA multisig that mints and burns one native shielded token,
|
|
10
10
|
* for tokenized deposits. Value enters only through `mint` (to a holder's
|
|
11
11
|
* coin public key) or `mintToSelf` (a coin this contract holds) and leaves
|
|
12
12
|
* only through `burn` (a holder's coin, paid into the tx) or `burnFromSelf`
|
|
13
|
-
* (a coin this contract holds); each is a single tx carrying
|
|
14
|
-
* signers' approvals
|
|
13
|
+
* (a coin this contract holds); each is a single tx carrying `t` registered
|
|
14
|
+
* signers' approvals.
|
|
15
15
|
*
|
|
16
|
-
* Composes `
|
|
16
|
+
* Composes `Secp256k1EcdsaMultisig` (signer commitments, approval checks),
|
|
17
17
|
* `NativeShieldedToken` as `NST_` (one token; domain, `name`, `symbol` and
|
|
18
18
|
* `decimals` fixed at construction) and `NativeShieldedTokenDerivedNonce` as
|
|
19
19
|
* `NSTDerivedNonce_` (coin nonces derived from a counter).
|
|
@@ -58,9 +58,9 @@ pragma language_version >= 0.26.0;
|
|
|
58
58
|
* uint256 amount,bytes32 coinNonce,uint256 coinValue)")`, written as
|
|
59
59
|
* literal words.
|
|
60
60
|
* - Signer commitment: `cm = H<SignerCommitmentInput>({ pkX, pkY, salt,
|
|
61
|
-
* domain: pad(32, "
|
|
62
|
-
* coordinates as `Bytes<32>`. `_calculateSignerId` is the
|
|
63
|
-
* circuit.
|
|
61
|
+
* domain: pad(32, "Secp256k1EcdsaMultisig:signer") })`, `pkX` / `pkY`
|
|
62
|
+
* the affine coordinates as `Bytes<32>`. `_calculateSignerId` is the
|
|
63
|
+
* exported pure circuit.
|
|
64
64
|
* - Coin nonce: `evolveNonce(counter, pad(32, "NativeShieldedToken:nonce"))`
|
|
65
65
|
* over the derived-nonce counter after its increment.
|
|
66
66
|
* - Encoding: `H` hashes the Compact encoding, not a byte concatenation.
|
|
@@ -72,25 +72,31 @@ pragma language_version >= 0.26.0;
|
|
|
72
72
|
*
|
|
73
73
|
* @notice Implementing contracts register the signer set from their
|
|
74
74
|
* constructor by calling this module's `initialize`, and MUST NOT import
|
|
75
|
-
* `
|
|
75
|
+
* `Signer` or `Secp256k1EcdsaMultisig` directly.
|
|
76
76
|
*
|
|
77
77
|
* @dev Parameterization:
|
|
78
|
-
*
|
|
78
|
+
* `t` is the approval width and the threshold, fixed at import
|
|
79
|
+
* (`"../presets/NativeShieldedTokenIssuer"<t>`). The implementing contract
|
|
80
|
+
* fixes the signer count `n` at `initialize`. Import one width per contract:
|
|
81
|
+
* two widths share one signer registry, see `Secp256k1EcdsaMultisig`.
|
|
82
|
+
* `NativeShieldedTokenIssuerExample` fixes 2-of-3. The `@constraints` tags on
|
|
83
|
+
* the gated circuits are measured at `t = 2`.
|
|
79
84
|
*
|
|
80
85
|
* @dev Compatibility:
|
|
81
86
|
* The wire format the custodian's signer implements is the EIP-712 domain
|
|
82
87
|
* (`NativeShieldedTokenIssuer`, version `1`, salt), the `Mint` /
|
|
83
|
-
* `MintToSelf` / `Burn` / `BurnFromSelf` type strings, the
|
|
84
|
-
*
|
|
85
|
-
* `
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
+
* `MintToSelf` / `Burn` / `BurnFromSelf` type strings, the
|
|
89
|
+
* `Secp256k1EcdsaMultisig:signer` commitment domain (owned by
|
|
90
|
+
* `Secp256k1EcdsaMultisig`), the `cm` preimage struct, and the
|
|
91
|
+
* `Vector<t>` / `Vector<n>` shapes the implementing contract fixes. Changing
|
|
92
|
+
* any of them breaks every existing signer setup. The test suite pins them
|
|
93
|
+
* through `EcdsaTestUtils.ts`.
|
|
88
94
|
*
|
|
89
95
|
* @notice Privacy:
|
|
90
96
|
* - Public, per tx:
|
|
91
97
|
* - Entry point: which of the four operations ran.
|
|
92
|
-
* - Approvers: the
|
|
93
|
-
* `Signer_assertSigner`), so WHICH
|
|
98
|
+
* - Approvers: the `t` signer commitments (`disclose(commitment)` in
|
|
99
|
+
* `Signer_assertSigner`), so WHICH signers approved.
|
|
94
100
|
* - Mint: `amount`, as the `shieldedMints` effect under the token domain,
|
|
95
101
|
* and the coin commitment as a spend claim. Recipient and nonce are not
|
|
96
102
|
* published. The nonce is `evolveNonce` of the public
|
|
@@ -149,12 +155,11 @@ pragma language_version >= 0.26.0;
|
|
|
149
155
|
* still refunds the bound `refundTo`.
|
|
150
156
|
* - Low-s: verification goes through `Ecdsa_secp256k1EcdsaVerifyLowS`, so a
|
|
151
157
|
* high-s encoding is an invalid signature.
|
|
152
|
-
* - Distinct signers:
|
|
153
|
-
*
|
|
154
|
-
* required. The threshold `2` is the only value `EcdsaSignerManager` accepts.
|
|
158
|
+
* - Distinct signers: every gated circuit verifies `t` distinct registered
|
|
159
|
+
* signers, so no set of fewer than `t` keys creates or destroys tokens.
|
|
155
160
|
* - Immutable signer set: no signer reconfiguration circuit is exported, so
|
|
156
|
-
* the
|
|
157
|
-
*
|
|
161
|
+
* the commitments and the threshold are fixed. Losing more than `n - t`
|
|
162
|
+
* keys freezes the contract.
|
|
158
163
|
* - Griefing: the derived-nonce mints are the only mint paths and both are
|
|
159
164
|
* threshold-gated, so `NativeShieldedTokenDerivedNonce`'s
|
|
160
165
|
* collision-griefing precondition (a caller-nonce mint at another trust
|
|
@@ -165,8 +170,8 @@ pragma language_version >= 0.26.0;
|
|
|
165
170
|
* @notice Concurrency:
|
|
166
171
|
* - Replay counter: `mint`, `mintToSelf`, `burn` and `burnFromSelf` read
|
|
167
172
|
* `_counter` into the digest. The read is pinned, so any two of them in one block
|
|
168
|
-
* conflict: at most one operation lands per block, and the losing
|
|
169
|
-
*
|
|
173
|
+
* conflict: at most one operation lands per block, and the losing signers
|
|
174
|
+
* re-sign at the next nonce.
|
|
170
175
|
* - Derived-nonce counter: both mints also pin it (`evolveNonce` consumes
|
|
171
176
|
* it); the `_counter` conflict already covers those pairs.
|
|
172
177
|
* - `getNonce`: submitted as a tx it pins `_counter` and conflicts with any
|
|
@@ -184,10 +189,10 @@ pragma language_version >= 0.26.0;
|
|
|
184
189
|
*
|
|
185
190
|
* @notice Client-side duties:
|
|
186
191
|
* - Before deploy: pick a random `instanceSalt`, derive each `cm` with
|
|
187
|
-
* `_calculateSignerId(pk, salt)`, pass
|
|
192
|
+
* `_calculateSignerId(pk, salt)`, pass them as `signerCommitments`.
|
|
188
193
|
* - Per operation: read `getNonce()`, rebuild the digest as
|
|
189
|
-
* `EcdsaTestUtils.ts` does, collect
|
|
190
|
-
* keys and signatures in matching `Vector<
|
|
194
|
+
* `EcdsaTestUtils.ts` does, collect `t` low-s signatures off-chain, submit
|
|
195
|
+
* keys and signatures in matching `Vector<t>` positions.
|
|
191
196
|
* - `mint`: hand the returned `ShieldedCoinInfo` to the recipient out of
|
|
192
197
|
* band; contract-initiated mints create no ciphertext, so a wallet cannot
|
|
193
198
|
* find the coin by scanning.
|
|
@@ -212,7 +217,7 @@ pragma language_version >= 0.26.0;
|
|
|
212
217
|
*
|
|
213
218
|
* @notice Scalability:
|
|
214
219
|
* - Throughput: one operation per block (see Concurrency).
|
|
215
|
-
* - State: constant-size, two counters and
|
|
220
|
+
* - State: constant-size, two counters and an `n`-entry signer set. Nothing
|
|
216
221
|
* grows per operation.
|
|
217
222
|
* - Amounts: mints cap at `Uint<64>` by `mintShieldedToken`. Burns take
|
|
218
223
|
* `Uint<128>`, the coin value range.
|
|
@@ -225,10 +230,10 @@ pragma language_version >= 0.26.0;
|
|
|
225
230
|
*
|
|
226
231
|
* @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
|
|
227
232
|
*/
|
|
228
|
-
module NativeShieldedTokenIssuer {
|
|
233
|
+
module NativeShieldedTokenIssuer<#t> {
|
|
229
234
|
import CompactStandardLibrary;
|
|
230
235
|
|
|
231
|
-
import "../
|
|
236
|
+
import "../Secp256k1EcdsaMultisig"<t> prefix Secp256k1EcdsaMultisig_;
|
|
232
237
|
import "../../crypto/Eip712" prefix Eip712_;
|
|
233
238
|
import "../../utils/EvmAbi" prefix EvmAbi_;
|
|
234
239
|
import "../../token/NativeShieldedToken" prefix NST_;
|
|
@@ -238,7 +243,7 @@ module NativeShieldedTokenIssuer {
|
|
|
238
243
|
// implementing contract can surface every ledger field under its own key.
|
|
239
244
|
// The circuits stay behind the prefixes above. The derived-nonce counter is
|
|
240
245
|
// aliased because this module's replay counter already owns `_counter`.
|
|
241
|
-
import { _instanceSalt, _signers, _signerCount, _threshold } from "../
|
|
246
|
+
import { _instanceSalt, _signers, _signerCount, _threshold } from "../Secp256k1EcdsaMultisig"<t>;
|
|
242
247
|
import { _domain, _name, _symbol, _decimals, _isInitialized } from "../../token/NativeShieldedToken";
|
|
243
248
|
import { _counter as _derivedNonceCounter } from "../../token/extensions/NativeShieldedTokenDerivedNonce";
|
|
244
249
|
export { _instanceSalt, _signers, _signerCount, _threshold };
|
|
@@ -262,9 +267,8 @@ module NativeShieldedTokenIssuer {
|
|
|
262
267
|
export sealed ledger _domainSeparator: Bytes<32>;
|
|
263
268
|
|
|
264
269
|
/**
|
|
265
|
-
* @description Registers the signer set
|
|
266
|
-
* and domain, and fixes the EIP-712 domain separator.
|
|
267
|
-
* fixed at 2.
|
|
270
|
+
* @description Registers the signer set at threshold `t`, initializes the
|
|
271
|
+
* token metadata and domain, and fixes the EIP-712 domain separator.
|
|
268
272
|
*
|
|
269
273
|
* @notice Call from the implementing contract's constructor only. It writes
|
|
270
274
|
* the token module's `sealed` domain and `_domainSeparator`, so
|
|
@@ -281,24 +285,25 @@ module NativeShieldedTokenIssuer {
|
|
|
281
285
|
*
|
|
282
286
|
* - `instanceSalt` should be cryptographically random.
|
|
283
287
|
* - `signerCommitments` must not contain duplicates.
|
|
288
|
+
* - `n` must be at least `t`.
|
|
284
289
|
*
|
|
285
290
|
* @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
|
|
286
291
|
* @param {Bytes<32>} tokenDomain - Domain separator for this contract's token color.
|
|
287
292
|
* @param {Opaque<"string">} name_ - The token name.
|
|
288
293
|
* @param {Opaque<"string">} symbol_ - The token symbol.
|
|
289
294
|
* @param {Uint<8>} decimals_ - The token decimals.
|
|
290
|
-
* @param {Vector<
|
|
295
|
+
* @param {Vector<n, Bytes<32>>} signerCommitments - Hashed signer identities.
|
|
291
296
|
* @returns {[]} Empty tuple.
|
|
292
297
|
*/
|
|
293
|
-
export circuit initialize(
|
|
298
|
+
export circuit initialize<#n>(
|
|
294
299
|
instanceSalt: Bytes<32>,
|
|
295
300
|
tokenDomain: Bytes<32>,
|
|
296
301
|
name_: Opaque<"string">,
|
|
297
302
|
symbol_: Opaque<"string">,
|
|
298
303
|
decimals_: Uint<8>,
|
|
299
|
-
signerCommitments: Vector<
|
|
304
|
+
signerCommitments: Vector<n, Bytes<32>>
|
|
300
305
|
): [] {
|
|
301
|
-
|
|
306
|
+
Secp256k1EcdsaMultisig_initialize<n>(instanceSalt, signerCommitments);
|
|
302
307
|
NST_initialize(tokenDomain, name_, symbol_, decimals_);
|
|
303
308
|
|
|
304
309
|
const hashedName = keccak256<Bytes<25>>(pad(25, "NativeShieldedTokenIssuer"));
|
|
@@ -394,7 +399,7 @@ module NativeShieldedTokenIssuer {
|
|
|
394
399
|
|
|
395
400
|
/**
|
|
396
401
|
* @description Computes a signer commitment from a secp256k1 public key,
|
|
397
|
-
* as `persistentHash(pkX, pkY, salt, "
|
|
402
|
+
* as `persistentHash(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`.
|
|
398
403
|
*
|
|
399
404
|
* Pure, so the deployer can derive constructor commitments off-chain.
|
|
400
405
|
*
|
|
@@ -410,7 +415,7 @@ module NativeShieldedTokenIssuer {
|
|
|
410
415
|
pk: Secp256k1Point,
|
|
411
416
|
salt: Bytes<32>
|
|
412
417
|
): Bytes<32> {
|
|
413
|
-
return
|
|
418
|
+
return Secp256k1EcdsaMultisig_calculateSignerId(pk, salt);
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
/**
|
|
@@ -423,27 +428,26 @@ module NativeShieldedTokenIssuer {
|
|
|
423
428
|
* @notice The returned coin info is the only copy available to the
|
|
424
429
|
* recipient; deliver it out of band.
|
|
425
430
|
*
|
|
426
|
-
* @constraints k=17, rows=
|
|
431
|
+
* @constraints k=17, rows=89807 (zkir-v3)
|
|
427
432
|
*
|
|
428
433
|
* Requirements:
|
|
429
434
|
*
|
|
430
|
-
* -
|
|
431
|
-
* -
|
|
432
|
-
* -
|
|
433
|
-
* - Threshold must be met.
|
|
435
|
+
* - No two entries may be the same signer.
|
|
436
|
+
* - Every public key must hash to a registered signer commitment.
|
|
437
|
+
* - Every signature must be valid over the mint message hash.
|
|
434
438
|
* - `recipient` is not zero.
|
|
435
439
|
*
|
|
436
440
|
* @param {Uint<64>} amount - The token amount to mint.
|
|
437
441
|
* @param {ZswapCoinPublicKey} recipient - The coin public key to receive the minted tokens.
|
|
438
|
-
* @param {Vector<
|
|
439
|
-
* @param {Vector<
|
|
442
|
+
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
443
|
+
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the mint hash.
|
|
440
444
|
* @returns {ShieldedCoinInfo} The minted coin (nonce, color, value).
|
|
441
445
|
*/
|
|
442
446
|
export circuit mint(
|
|
443
447
|
amount: Uint<64>,
|
|
444
448
|
recipient: ZswapCoinPublicKey,
|
|
445
|
-
pubkeys: Vector<
|
|
446
|
-
signatures: Vector<
|
|
449
|
+
pubkeys: Vector<t, Secp256k1Point>,
|
|
450
|
+
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
447
451
|
): ShieldedCoinInfo {
|
|
448
452
|
const opNonce = _counter;
|
|
449
453
|
_counter.increment(1);
|
|
@@ -459,7 +463,7 @@ module NativeShieldedTokenIssuer {
|
|
|
459
463
|
EvmAbi_uint64Word(amount)
|
|
460
464
|
]));
|
|
461
465
|
|
|
462
|
-
|
|
466
|
+
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
463
467
|
|
|
464
468
|
return NST__mint(recipient, amount, NSTDerivedNonce__deriveNonce());
|
|
465
469
|
}
|
|
@@ -472,24 +476,23 @@ module NativeShieldedTokenIssuer {
|
|
|
472
476
|
* @notice The returned coin info is the contract's only record of the
|
|
473
477
|
* holding; persist it for `burnFromSelf`.
|
|
474
478
|
*
|
|
475
|
-
* @constraints k=17, rows=
|
|
479
|
+
* @constraints k=17, rows=85586 (zkir-v3)
|
|
476
480
|
*
|
|
477
481
|
* Requirements:
|
|
478
482
|
*
|
|
479
|
-
* -
|
|
480
|
-
* -
|
|
481
|
-
* -
|
|
482
|
-
* - Threshold must be met.
|
|
483
|
+
* - No two entries may be the same signer.
|
|
484
|
+
* - Every public key must hash to a registered signer commitment.
|
|
485
|
+
* - Every signature must be valid over the mint-to-self message hash.
|
|
483
486
|
*
|
|
484
487
|
* @param {Uint<64>} amount - The token amount to mint.
|
|
485
|
-
* @param {Vector<
|
|
486
|
-
* @param {Vector<
|
|
488
|
+
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
489
|
+
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the mint-to-self hash.
|
|
487
490
|
* @returns {ShieldedCoinInfo} The minted coin (nonce, color, value).
|
|
488
491
|
*/
|
|
489
492
|
export circuit mintToSelf(
|
|
490
493
|
amount: Uint<64>,
|
|
491
|
-
pubkeys: Vector<
|
|
492
|
-
signatures: Vector<
|
|
494
|
+
pubkeys: Vector<t, Secp256k1Point>,
|
|
495
|
+
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
493
496
|
): ShieldedCoinInfo {
|
|
494
497
|
const opNonce = _counter;
|
|
495
498
|
_counter.increment(1);
|
|
@@ -504,7 +507,7 @@ module NativeShieldedTokenIssuer {
|
|
|
504
507
|
EvmAbi_uint64Word(amount)
|
|
505
508
|
]));
|
|
506
509
|
|
|
507
|
-
|
|
510
|
+
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
508
511
|
|
|
509
512
|
return NST__mintToSelf(amount, NSTDerivedNonce__deriveNonce());
|
|
510
513
|
}
|
|
@@ -517,14 +520,13 @@ module NativeShieldedTokenIssuer {
|
|
|
517
520
|
* @notice The returned refund coin info is the only copy available to
|
|
518
521
|
* `refundTo`; deliver it out of band.
|
|
519
522
|
*
|
|
520
|
-
* @constraints k=17, rows=
|
|
523
|
+
* @constraints k=17, rows=119818 (zkir-v3)
|
|
521
524
|
*
|
|
522
525
|
* Requirements:
|
|
523
526
|
*
|
|
524
|
-
* -
|
|
525
|
-
* -
|
|
526
|
-
* -
|
|
527
|
-
* - Threshold must be met.
|
|
527
|
+
* - No two entries may be the same signer.
|
|
528
|
+
* - Every public key must hash to a registered signer commitment.
|
|
529
|
+
* - Every signature must be valid over the burn message hash.
|
|
528
530
|
* - `coin.color` is this contract's token color.
|
|
529
531
|
* - `amount` is less than or equal to `coin.value`.
|
|
530
532
|
* - `refundTo` is not zero.
|
|
@@ -532,8 +534,8 @@ module NativeShieldedTokenIssuer {
|
|
|
532
534
|
* @param {ShieldedCoinInfo} coin - The holder's coin to burn from.
|
|
533
535
|
* @param {Uint<128>} amount - The token amount to burn.
|
|
534
536
|
* @param {ZswapCoinPublicKey} refundTo - Receives `coin.value - amount`.
|
|
535
|
-
* @param {Vector<
|
|
536
|
-
* @param {Vector<
|
|
537
|
+
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
538
|
+
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the burn hash.
|
|
537
539
|
* @returns {Maybe<ShieldedCoinInfo>} The refund coin created for `refundTo`,
|
|
538
540
|
* or `none` if the coin was burned in full.
|
|
539
541
|
*/
|
|
@@ -541,8 +543,8 @@ module NativeShieldedTokenIssuer {
|
|
|
541
543
|
coin: ShieldedCoinInfo,
|
|
542
544
|
amount: Uint<128>,
|
|
543
545
|
refundTo: ZswapCoinPublicKey,
|
|
544
|
-
pubkeys: Vector<
|
|
545
|
-
signatures: Vector<
|
|
546
|
+
pubkeys: Vector<t, Secp256k1Point>,
|
|
547
|
+
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
546
548
|
): Maybe<ShieldedCoinInfo> {
|
|
547
549
|
const opNonce = _counter;
|
|
548
550
|
_counter.increment(1);
|
|
@@ -558,7 +560,7 @@ module NativeShieldedTokenIssuer {
|
|
|
558
560
|
EvmAbi_uint128Word(amount)
|
|
559
561
|
]));
|
|
560
562
|
|
|
561
|
-
|
|
563
|
+
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
562
564
|
|
|
563
565
|
return NST__burn(coin, amount, refundTo);
|
|
564
566
|
}
|
|
@@ -575,30 +577,29 @@ module NativeShieldedTokenIssuer {
|
|
|
575
577
|
* The digest binds `coin.nonce` and `coin.value`, so approvals authorize one
|
|
576
578
|
* specific holding rather than an amount.
|
|
577
579
|
*
|
|
578
|
-
* @constraints k=17, rows=
|
|
580
|
+
* @constraints k=17, rows=104973 (zkir-v3)
|
|
579
581
|
*
|
|
580
582
|
* Requirements:
|
|
581
583
|
*
|
|
582
|
-
* -
|
|
583
|
-
* -
|
|
584
|
-
* -
|
|
585
|
-
* - Threshold must be met.
|
|
584
|
+
* - No two entries may be the same signer.
|
|
585
|
+
* - Every public key must hash to a registered signer commitment.
|
|
586
|
+
* - Every signature must be valid over the burn-from-self message hash.
|
|
586
587
|
* - `coin.color` is this contract's token color.
|
|
587
588
|
* - `amount` is less than or equal to `coin.value`.
|
|
588
589
|
* - `coin.nonce` and `coin.value` match the approved coin.
|
|
589
590
|
*
|
|
590
591
|
* @param {QualifiedShieldedCoinInfo} coin - The contract-held coin to burn from.
|
|
591
592
|
* @param {Uint<128>} amount - The token amount to burn.
|
|
592
|
-
* @param {Vector<
|
|
593
|
-
* @param {Vector<
|
|
593
|
+
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
594
|
+
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the burn-from-self hash.
|
|
594
595
|
* @returns {Maybe<ShieldedCoinInfo>} The change coin retained by the
|
|
595
596
|
* contract, or `none` if the coin was burned in full.
|
|
596
597
|
*/
|
|
597
598
|
export circuit burnFromSelf(
|
|
598
599
|
coin: QualifiedShieldedCoinInfo,
|
|
599
600
|
amount: Uint<128>,
|
|
600
|
-
pubkeys: Vector<
|
|
601
|
-
signatures: Vector<
|
|
601
|
+
pubkeys: Vector<t, Secp256k1Point>,
|
|
602
|
+
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
602
603
|
): Maybe<ShieldedCoinInfo> {
|
|
603
604
|
const opNonce = _counter;
|
|
604
605
|
_counter.increment(1);
|
|
@@ -615,7 +616,7 @@ module NativeShieldedTokenIssuer {
|
|
|
615
616
|
EvmAbi_uint128Word(coin.value)
|
|
616
617
|
]));
|
|
617
618
|
|
|
618
|
-
|
|
619
|
+
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
619
620
|
|
|
620
621
|
return NST__burnFromSelf(coin, amount);
|
|
621
622
|
}
|
|
@@ -684,7 +685,7 @@ module NativeShieldedTokenIssuer {
|
|
|
684
685
|
* @returns {Uint<8>} The signer count.
|
|
685
686
|
*/
|
|
686
687
|
export circuit getSignerCount(): Uint<8> {
|
|
687
|
-
return
|
|
688
|
+
return Secp256k1EcdsaMultisig_getSignerCount();
|
|
688
689
|
}
|
|
689
690
|
|
|
690
691
|
/**
|
|
@@ -695,7 +696,7 @@ module NativeShieldedTokenIssuer {
|
|
|
695
696
|
* @returns {Uint<8>} The threshold.
|
|
696
697
|
*/
|
|
697
698
|
export circuit getThreshold(): Uint<8> {
|
|
698
|
-
return
|
|
699
|
+
return Secp256k1EcdsaMultisig_getThreshold();
|
|
699
700
|
}
|
|
700
701
|
|
|
701
702
|
/**
|
|
@@ -707,6 +708,6 @@ module NativeShieldedTokenIssuer {
|
|
|
707
708
|
* @returns {Boolean} True if registered.
|
|
708
709
|
*/
|
|
709
710
|
export circuit isSigner(commitment: Bytes<32>): Boolean {
|
|
710
|
-
return
|
|
711
|
+
return Secp256k1EcdsaMultisig_isSigner(commitment);
|
|
711
712
|
}
|
|
712
713
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.1 (multisig/presets/ShieldedMultiSigV2.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module ShieldedMultiSigV2
|
|
8
|
-
* @description Privacy-preserving
|
|
8
|
+
* @description Privacy-preserving threshold multisig contract.
|
|
9
9
|
*
|
|
10
10
|
* Signer identities are stored as commitments: hashes of ECDSA public
|
|
11
11
|
* keys combined with an instance salt and domain separator. Signature
|
|
@@ -14,6 +14,12 @@ pragma language_version >= 0.26.0;
|
|
|
14
14
|
* and replay protection. All other coordination (signature collection,
|
|
15
15
|
* coin selection) happens off-chain.
|
|
16
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
|
+
*
|
|
17
23
|
* Treasury is fully stateless meaning coin data is not stored on the public ledger.
|
|
18
24
|
* Deposits call receiveShielded only. The operator discovers coin indices
|
|
19
25
|
* through ZswapOutput events from the indexer, constructs QualifiedShieldedCoinInfo
|
|
@@ -23,12 +29,12 @@ pragma language_version >= 0.26.0;
|
|
|
23
29
|
*
|
|
24
30
|
* @notice Implementing contracts register the signer set from their
|
|
25
31
|
* constructor by calling this module's `initialize`, and MUST NOT import
|
|
26
|
-
* `
|
|
32
|
+
* `Signer` or `Secp256k1EcdsaMultisig` directly.
|
|
27
33
|
*/
|
|
28
|
-
module ShieldedMultiSigV2 {
|
|
34
|
+
module ShieldedMultiSigV2<#t> {
|
|
29
35
|
import CompactStandardLibrary;
|
|
30
36
|
|
|
31
|
-
import "../
|
|
37
|
+
import "../Secp256k1EcdsaMultisig"<t> prefix Secp256k1EcdsaMultisig_;
|
|
32
38
|
import "../../crypto/Eip712" prefix Eip712_;
|
|
33
39
|
import "../../utils/EvmAbi" prefix EvmAbi_;
|
|
34
40
|
import "../ProposalManager" prefix Proposal_;
|
|
@@ -39,8 +45,6 @@ module ShieldedMultiSigV2 {
|
|
|
39
45
|
// unwritten copy of its state.
|
|
40
46
|
export { Proposal_Recipient };
|
|
41
47
|
|
|
42
|
-
// ─── State ──────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
48
|
/**
|
|
45
49
|
* @description Monotonic operation counter. `execute` reads it as that
|
|
46
50
|
* operation's nonce and then increments, so no two executions share one.
|
|
@@ -54,10 +58,8 @@ module ShieldedMultiSigV2 {
|
|
|
54
58
|
*/
|
|
55
59
|
export sealed ledger _domainSeparator: Bytes<32>;
|
|
56
60
|
|
|
57
|
-
// ─── Initialization ─────────────────────────────────────────────
|
|
58
|
-
|
|
59
61
|
/**
|
|
60
|
-
* @description Registers the signer set
|
|
62
|
+
* @description Registers the signer set at threshold `t` and fixes the
|
|
61
63
|
* EIP-712 domain separator.
|
|
62
64
|
*
|
|
63
65
|
* @notice Call from the implementing contract's constructor only. It writes
|
|
@@ -82,19 +84,17 @@ module ShieldedMultiSigV2 {
|
|
|
82
84
|
*
|
|
83
85
|
* - `instanceSalt` should be cryptographically random.
|
|
84
86
|
* - `signerCommitments` must not contain duplicates.
|
|
85
|
-
* - `
|
|
87
|
+
* - `n` must be at least `t`.
|
|
86
88
|
*
|
|
87
89
|
* @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
|
|
88
|
-
* @param {Vector<
|
|
89
|
-
* @param {Uint<8>} thresh - Approvals required. Must be 2.
|
|
90
|
+
* @param {Vector<n, Bytes<32>>} signerCommitments - Hashed signer identities.
|
|
90
91
|
* @returns {[]} Empty tuple.
|
|
91
92
|
*/
|
|
92
|
-
export circuit initialize(
|
|
93
|
+
export circuit initialize<#n>(
|
|
93
94
|
instanceSalt: Bytes<32>,
|
|
94
|
-
signerCommitments: Vector<
|
|
95
|
-
thresh: Uint<8>
|
|
95
|
+
signerCommitments: Vector<n, Bytes<32>>
|
|
96
96
|
): [] {
|
|
97
|
-
|
|
97
|
+
Secp256k1EcdsaMultisig_initialize<n>(instanceSalt, signerCommitments);
|
|
98
98
|
|
|
99
99
|
const hashedName = keccak256<Bytes<18>>(pad(18, "ShieldedMultiSigV2"));
|
|
100
100
|
const hashedVersion = keccak256<Bytes<1>>(pad(1, "1"));
|
|
@@ -127,8 +127,6 @@ module ShieldedMultiSigV2 {
|
|
|
127
127
|
return typeHash as Bytes<32>;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
// ─── Deposit ────────────────────────────────────────────────────
|
|
131
|
-
|
|
132
130
|
/**
|
|
133
131
|
* @description Receives a shielded coin into the multisig treasury.
|
|
134
132
|
*
|
|
@@ -149,8 +147,6 @@ module ShieldedMultiSigV2 {
|
|
|
149
147
|
receiveShielded(disclose(coin));
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
// ─── Execute ────────────────────────────────────────────────────
|
|
153
|
-
|
|
154
150
|
/**
|
|
155
151
|
* @description Executes a shielded send authorized by threshold signatures.
|
|
156
152
|
*
|
|
@@ -161,29 +157,29 @@ module ShieldedMultiSigV2 {
|
|
|
161
157
|
* separator. The digest is bound to the contract instance and the full recipient (kind and
|
|
162
158
|
* address), so a signature authorizes exactly one operation on one instance.
|
|
163
159
|
*
|
|
164
|
-
* Approvals are checked by `
|
|
165
|
-
* commitment from their public key and the instance salt
|
|
166
|
-
*
|
|
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`.
|
|
167
163
|
*
|
|
168
164
|
* @notice `to` must be a `ShieldedUser`. A contract-addressed coin would be
|
|
169
165
|
* unclaimable, making the transaction malformed.
|
|
170
166
|
*
|
|
171
|
-
* @constraints k=17, rows=
|
|
167
|
+
* @constraints k=17, rows=98969 (zkir-v3)
|
|
172
168
|
*
|
|
173
169
|
* Requirements:
|
|
174
170
|
*
|
|
175
|
-
* -
|
|
176
|
-
* -
|
|
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
|
|
177
174
|
* encoding.
|
|
178
|
-
* - Signers must not be duplicates.
|
|
179
175
|
* - `to.kind` must be `ShieldedUser`.
|
|
180
176
|
* - Coin value must be >= amount.
|
|
181
177
|
*
|
|
182
178
|
* @param {Proposal_Recipient} to - The recipient.
|
|
183
179
|
* @param {Uint<128>} amount - The amount to send.
|
|
184
180
|
* @param {QualifiedShieldedCoinInfo} coin - The coin to spend (from operator's pool).
|
|
185
|
-
* @param {Vector<
|
|
186
|
-
* @param {Vector<
|
|
181
|
+
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
182
|
+
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the operation.
|
|
187
183
|
*
|
|
188
184
|
* @returns {ShieldedSendResult} The send result including any change.
|
|
189
185
|
*/
|
|
@@ -191,8 +187,8 @@ module ShieldedMultiSigV2 {
|
|
|
191
187
|
to: Proposal_Recipient,
|
|
192
188
|
amount: Uint<128>,
|
|
193
189
|
coin: QualifiedShieldedCoinInfo,
|
|
194
|
-
pubkeys: Vector<
|
|
195
|
-
signatures: Vector<
|
|
190
|
+
pubkeys: Vector<t, Secp256k1Point>,
|
|
191
|
+
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
196
192
|
): ShieldedSendResult {
|
|
197
193
|
// Increment nonce
|
|
198
194
|
const currentNonce = _nonce;
|
|
@@ -212,11 +208,11 @@ module ShieldedMultiSigV2 {
|
|
|
212
208
|
EvmAbi_uint128Word(amount)
|
|
213
209
|
]));
|
|
214
210
|
|
|
215
|
-
|
|
211
|
+
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
216
212
|
|
|
217
213
|
assert(
|
|
218
214
|
to.kind == Proposal_RecipientKind.ShieldedUser,
|
|
219
|
-
"
|
|
215
|
+
"ShieldedMultiSigV2: recipient must be a coin public key"
|
|
220
216
|
);
|
|
221
217
|
const recipient = left<ZswapCoinPublicKey, ContractAddress>(
|
|
222
218
|
ZswapCoinPublicKey { bytes: to.address }
|
|
@@ -224,11 +220,9 @@ module ShieldedMultiSigV2 {
|
|
|
224
220
|
return Treasury__send(coin, recipient, amount);
|
|
225
221
|
}
|
|
226
222
|
|
|
227
|
-
// ─── Commitments ────────────────────────────────────────────────
|
|
228
|
-
|
|
229
223
|
/**
|
|
230
224
|
* @description Computes a signer commitment from a secp256k1 public key,
|
|
231
|
-
* as `persistentHash(pkX, pkY, salt, "
|
|
225
|
+
* as `persistentHash(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`.
|
|
232
226
|
*
|
|
233
227
|
* Pure, so the deployer can derive constructor commitments off-chain.
|
|
234
228
|
*
|
|
@@ -245,11 +239,9 @@ module ShieldedMultiSigV2 {
|
|
|
245
239
|
pk: Secp256k1Point,
|
|
246
240
|
salt: Bytes<32>
|
|
247
241
|
): Bytes<32> {
|
|
248
|
-
return
|
|
242
|
+
return Secp256k1EcdsaMultisig_calculateSignerId(pk, salt);
|
|
249
243
|
}
|
|
250
244
|
|
|
251
|
-
// ─── View ───────────────────────────────────────────────────────
|
|
252
|
-
|
|
253
245
|
/**
|
|
254
246
|
* @description Returns the nonce the next `execute` digest will bind to.
|
|
255
247
|
*
|
|
@@ -269,7 +261,7 @@ module ShieldedMultiSigV2 {
|
|
|
269
261
|
* @returns {Uint<8>} The signer count.
|
|
270
262
|
*/
|
|
271
263
|
export circuit getSignerCount(): Uint<8> {
|
|
272
|
-
return
|
|
264
|
+
return Secp256k1EcdsaMultisig_getSignerCount();
|
|
273
265
|
}
|
|
274
266
|
|
|
275
267
|
/**
|
|
@@ -280,7 +272,7 @@ module ShieldedMultiSigV2 {
|
|
|
280
272
|
* @returns {Uint<8>} The threshold.
|
|
281
273
|
*/
|
|
282
274
|
export circuit getThreshold(): Uint<8> {
|
|
283
|
-
return
|
|
275
|
+
return Secp256k1EcdsaMultisig_getThreshold();
|
|
284
276
|
}
|
|
285
277
|
|
|
286
278
|
/**
|
|
@@ -292,6 +284,6 @@ module ShieldedMultiSigV2 {
|
|
|
292
284
|
* @returns {Boolean} True if registered.
|
|
293
285
|
*/
|
|
294
286
|
export circuit isSigner(commitment: Bytes<32>): Boolean {
|
|
295
|
-
return
|
|
287
|
+
return Secp256k1EcdsaMultisig_isSigner(commitment);
|
|
296
288
|
}
|
|
297
289
|
}
|
package/package.json
CHANGED