@openzeppelin/compact-contracts 0.4.0-alpha.5 → 0.4.0-alpha.6

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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/access/AccessControl.compact +1 -1
  3. package/access/Ownable.compact +1 -1
  4. package/access/ShieldedAccessControl.compact +1 -1
  5. package/access/ZOwnablePK.compact +13 -4
  6. package/crypto/EcdhMask.compact +1 -1
  7. package/crypto/Ecdsa.compact +1 -1
  8. package/crypto/Eip712.compact +1 -1
  9. package/crypto/ElGamal.compact +1 -1
  10. package/multisig/ForwarderPrivate.compact +1 -1
  11. package/multisig/ForwarderShielded.compact +1 -1
  12. package/multisig/ForwarderUnshielded.compact +1 -1
  13. package/multisig/ProposalManager.compact +1 -1
  14. package/multisig/Secp256k1EcdsaMultisig.compact +275 -0
  15. package/multisig/ShieldedTreasury.compact +1 -1
  16. package/multisig/ShieldedTreasuryStateless.compact +1 -1
  17. package/multisig/Signer.compact +1 -1
  18. package/multisig/UnshieldedTreasury.compact +1 -1
  19. package/multisig/examples/ForwarderPrivateExample.compact +1 -1
  20. package/multisig/examples/ForwarderShieldedExample.compact +1 -1
  21. package/multisig/examples/ForwarderUnshieldedExample.compact +1 -1
  22. package/multisig/examples/NativeShieldedTokenIssuerExample.compact +4 -4
  23. package/multisig/examples/ShieldedMultiSigV2Example.compact +6 -8
  24. package/multisig/presets/NativeShieldedTokenIssuer.compact +84 -83
  25. package/multisig/presets/ShieldedMultiSigV2.compact +35 -43
  26. package/package.json +1 -1
  27. package/security/Allowlist.compact +1 -1
  28. package/security/Blocklist.compact +1 -1
  29. package/security/Initializable.compact +1 -1
  30. package/security/Pausable.compact +1 -1
  31. package/token/ConfidentialFungibleToken.compact +1 -1
  32. package/token/FungibleToken.compact +1 -1
  33. package/token/MultiToken.compact +1 -1
  34. package/token/NativeShieldedToken.compact +1 -1
  35. package/token/NativeShieldedTokenCore.compact +1 -1
  36. package/token/NativeShieldedTokenFamily.compact +1 -1
  37. package/token/NonFungibleToken.compact +1 -1
  38. package/token/examples/ZOwnableNativeShieldedTokenExample.compact +109 -0
  39. package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
  40. package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
  41. package/token/presets/ZOwnableNativeShieldedToken.compact +314 -0
  42. package/utils/EvmAbi.compact +1 -1
  43. package/utils/Utils.compact +1 -1
  44. package/multisig/EcdsaSignerManager.compact +0 -248
@@ -1,19 +1,19 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (multisig/presets/NativeShieldedTokenIssuer.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (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 2-of-3 ECDSA multisig that mints and burns one native shielded token,
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 two registered
14
- * signers' approvals, so no single party creates or destroys tokens.
13
+ * (a coin this contract holds); each is a single tx carrying `t` registered
14
+ * signers' approvals.
15
15
  *
16
- * Composes `EcdsaSignerManager` (signer commitments, approval checks),
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, "multisig:signer:") })`, `pkX` / `pkY` the affine
62
- * coordinates as `Bytes<32>`. `_calculateSignerId` is the exported pure
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
- * `EcdsaSignerManager` directly.
75
+ * `Signer` or `Secp256k1EcdsaMultisig` directly.
76
76
  *
77
77
  * @dev Parameterization:
78
- * None: the signer set is hardcoded to 3 and the threshold to 2.
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 `multisig:signer:` commitment domain (generic prefix,
84
- * owned by `EcdsaSignerManager`), the `cm` preimage struct, and the
85
- * `Vector<2>` / `Vector<3>` shapes. Changing any of them breaks every
86
- * existing signer setup. The test suite pins them through
87
- * `EcdsaTestUtils.ts`.
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 two signer commitments (`disclose(commitment)` in
93
- * `Signer_assertSigner`), so WHICH two of the three signers approved.
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: `assertApprovals` asserts both entries and rejects a
153
- * repeated commitment, so two distinct registered signers are always
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 three commitments and the threshold are fixed. Losing two of the
157
- * three keys freezes the contract.
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 signer
169
- * pair re-signs at the next nonce.
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 the three as `signerCommitments`.
192
+ * `_calculateSignerId(pk, salt)`, pass them as `signerCommitments`.
188
193
  * - Per operation: read `getNonce()`, rebuild the digest as
189
- * `EcdsaTestUtils.ts` does, collect two low-s signatures off-chain, submit
190
- * keys and signatures in matching `Vector<2>` positions.
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 a three-entry signer set. Nothing
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 "../EcdsaSignerManager" prefix EcdsaSignerManager_;
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 "../EcdsaSignerManager";
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, initializes the token metadata
266
- * and domain, and fixes the EIP-712 domain separator. The threshold is
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<3, Bytes<32>>} signerCommitments - Hashed signer identities.
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<3, Bytes<32>>,
304
+ signerCommitments: Vector<n, Bytes<32>>
300
305
  ): [] {
301
- EcdsaSignerManager_initialize<3>(instanceSalt, signerCommitments, 2);
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, "multisig:signer:")`.
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 EcdsaSignerManager_calculateSignerId(pk, salt);
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=89815 (zkir-v3)
431
+ * @constraints k=17, rows=89807 (zkir-v3)
427
432
  *
428
433
  * Requirements:
429
434
  *
430
- * - Both public keys must hash to registered signer commitments.
431
- * - Both signatures must be valid over the mint message hash.
432
- * - Signers must not be duplicates.
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<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
439
- * @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the mint hash.
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<2, Secp256k1Point>,
446
- signatures: Vector<2, Secp256k1EcdsaSignature>
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
- EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
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=85594 (zkir-v3)
479
+ * @constraints k=17, rows=85586 (zkir-v3)
476
480
  *
477
481
  * Requirements:
478
482
  *
479
- * - Both public keys must hash to registered signer commitments.
480
- * - Both signatures must be valid over the mint-to-self message hash.
481
- * - Signers must not be duplicates.
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<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
486
- * @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the mint-to-self hash.
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<2, Secp256k1Point>,
492
- signatures: Vector<2, Secp256k1EcdsaSignature>
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
- EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
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=119826 (zkir-v3)
523
+ * @constraints k=17, rows=119818 (zkir-v3)
521
524
  *
522
525
  * Requirements:
523
526
  *
524
- * - Both public keys must hash to registered signer commitments.
525
- * - Both signatures must be valid over the burn message hash.
526
- * - Signers must not be duplicates.
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<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
536
- * @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the burn hash.
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<2, Secp256k1Point>,
545
- signatures: Vector<2, Secp256k1EcdsaSignature>
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
- EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
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=104981 (zkir-v3)
580
+ * @constraints k=17, rows=104973 (zkir-v3)
579
581
  *
580
582
  * Requirements:
581
583
  *
582
- * - Both public keys must hash to registered signer commitments.
583
- * - Both signatures must be valid over the burn-from-self message hash.
584
- * - Signers must not be duplicates.
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<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
593
- * @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the burn-from-self hash.
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<2, Secp256k1Point>,
601
- signatures: Vector<2, Secp256k1EcdsaSignature>
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
- EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
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 EcdsaSignerManager_getSignerCount();
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 EcdsaSignerManager_getThreshold();
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 EcdsaSignerManager_isSigner(commitment);
711
+ return Secp256k1EcdsaMultisig_isSigner(commitment);
711
712
  }
712
713
  }
@@ -1,11 +1,11 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (multisig/presets/ShieldedMultiSigV2.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (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 2-of-3 multisig contract.
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
- * `EcdsaSignerManager` directly.
32
+ * `Signer` or `Secp256k1EcdsaMultisig` directly.
27
33
  */
28
- module ShieldedMultiSigV2 {
34
+ module ShieldedMultiSigV2<#t> {
29
35
  import CompactStandardLibrary;
30
36
 
31
- import "../EcdsaSignerManager" prefix EcdsaSignerManager_;
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 and threshold, and fixes the
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
- * - `thresh` must be 2, the number of signatures `execute` verifies.
87
+ * - `n` must be at least `t`.
86
88
  *
87
89
  * @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
88
- * @param {Vector<3, Bytes<32>>} signerCommitments - Hashed signer identities.
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<3, Bytes<32>>,
95
- thresh: Uint<8>
95
+ signerCommitments: Vector<n, Bytes<32>>
96
96
  ): [] {
97
- EcdsaSignerManager_initialize<3>(instanceSalt, signerCommitments, thresh);
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 `EcdsaSignerManager`, which derives each signer's
165
- * commitment from their public key and the instance salt, rejects duplicates,
166
- * and enforces the threshold.
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=98977 (zkir-v3)
167
+ * @constraints k=17, rows=98969 (zkir-v3)
172
168
  *
173
169
  * Requirements:
174
170
  *
175
- * - Both public keys must hash to registered signer commitments.
176
- * - Both signatures must be valid over the message hash and use the low-s
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<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
186
- * @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the operation.
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<2, Secp256k1Point>,
195
- signatures: Vector<2, Secp256k1EcdsaSignature>
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
- EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
211
+ Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
216
212
 
217
213
  assert(
218
214
  to.kind == Proposal_RecipientKind.ShieldedUser,
219
- "Multisig: recipient must be a coin public key"
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, "multisig:signer:")`.
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 EcdsaSignerManager_calculateSignerId(pk, salt);
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 EcdsaSignerManager_getSignerCount();
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 EcdsaSignerManager_getThreshold();
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 EcdsaSignerManager_isSigner(commitment);
287
+ return Secp256k1EcdsaMultisig_isSigner(commitment);
296
288
  }
297
289
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/compact-contracts",
3
- "version": "0.4.0-alpha.5",
3
+ "version": "0.4.0-alpha.6",
4
4
  "description": "OpenZeppelin Compact contract library",
5
5
  "keywords": [
6
6
  "openzeppelin",
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (security/Allowlist.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (security/Allowlist.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.4.0-alpha.5 (security/Blocklist.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (security/Blocklist.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5