@openzeppelin/compact-contracts 0.4.0-alpha.4 → 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 +14 -5
  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 +5 -3
  19. package/multisig/examples/ForwarderPrivateExample.compact +1 -1
  20. package/multisig/examples/ForwarderShieldedExample.compact +8 -5
  21. package/multisig/examples/ForwarderUnshieldedExample.compact +1 -1
  22. package/multisig/examples/NativeShieldedTokenIssuerExample.compact +10 -8
  23. package/multisig/examples/ShieldedMultiSigV2Example.compact +6 -8
  24. package/multisig/presets/NativeShieldedTokenIssuer.compact +126 -108
  25. package/multisig/presets/ShieldedMultiSigV2.compact +46 -45
  26. package/package.json +2 -2
  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 +17 -1
  32. package/token/FungibleToken.compact +1 -1
  33. package/token/MultiToken.compact +1 -1
  34. package/token/NativeShieldedToken.compact +2 -2
  35. package/token/NativeShieldedTokenCore.compact +11 -18
  36. package/token/NativeShieldedTokenFamily.compact +2 -2
  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 -246
package/README.md CHANGED
@@ -191,7 +191,7 @@ Overall progress [====================] 14/14
191
191
 
192
192
  ZKIR v3: the library targets ZKIR v2, the compiler 0.34.0 default.
193
193
  Pass `--feature-zkir-v3` only when the contract reaches a module that needs it:
194
- the secp256k1-based `crypto/Ecdsa` and `multisig/EcdsaSignerManager`,
194
+ the secp256k1-based `crypto/Ecdsa` and `multisig/Secp256k1EcdsaMultisig`,
195
195
  or the `keccak256`-based `crypto/Eip712`.
196
196
  `multisig/presets/ShieldedMultiSigV2`, `multisig/presets/NativeShieldedTokenIssuer`
197
197
  and the `multisig/examples/` contracts reach both.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.4 (access/AccessControl.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (access/AccessControl.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.4 (access/Ownable.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (access/Ownable.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.4 (access/ShieldedAccessControl.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (access/ShieldedAccessControl.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.4 (access/ZOwnablePK.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (access/ZOwnablePK.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -35,10 +35,17 @@ pragma language_version >= 0.26.0;
35
35
  * providing unlinkability across ownership changes.
36
36
  *
37
37
  * @dev Security Considerations:
38
- * - The `secretNonce` must be kept private. Loss of the nonce prevents the
39
- * owner from proving ownership or transferring it.
38
+ * - `ownPublicKey()` is a witness: it returns the caller's Zswap coin public
39
+ * key, and nothing proves the caller holds the matching private key. See
40
+ * https://docs.midnight.network/compact/smart-contract-security#ownpublickey-is-a-witness-function.
41
+ * - The `secretNonce` is therefore the owner credential: the nonce and the
42
+ * owner's public key are enough to pass `assertOnlyOwner`. Generate it as
43
+ * 32 bytes from a CSPRNG and keep it private. Losing it prevents the owner
44
+ * from proving ownership or transferring it.
40
45
  * - Ownership validation is entirely circuit-based using witness-provided values.
41
46
  * - The `_instanceSalt` is immutable and used to differentiate deployments.
47
+ * Use a fresh random value per deployment; a reused salt links the same
48
+ * owner across deployments.
42
49
  *
43
50
  * @notice Best used for single-admin contracts with privacy requirements.
44
51
  * It is not designed for multi-owner or role-based access control.
@@ -99,12 +106,13 @@ module ZOwnablePK {
99
106
  * @warning The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm.
100
107
  * Using any other algorithm will result in a permanent loss of contract access.
101
108
  *
102
- * @constraints k=14, rows=14933
109
+ * @constraints k=13, rows=6765
103
110
  *
104
111
  * Requirements:
105
112
  *
106
113
  * - Contract is not initialized.
107
114
  * - `ownerId` is not zero.
115
+ * - `instanceSalt` is not zero.
108
116
  *
109
117
  * @param {Bytes<32>} ownerId - The owner's unique identifier SHA256(pk, nonce).
110
118
  * @param {Bytes<32>} instanceSalt - Contract salt to prevent duplicate commitments if
@@ -116,6 +124,7 @@ module ZOwnablePK {
116
124
  _isInitialized = true;
117
125
 
118
126
  assert(ownerId != default<Bytes<32>>, "ZOwnablePK: invalid id");
127
+ assert(instanceSalt != default<Bytes<32>>, "ZOwnablePK: invalid salt");
119
128
  _instanceSalt = disclose(instanceSalt);
120
129
  _transferOwnership(ownerId);
121
130
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.4 (crypto/EcdhMask.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (crypto/EcdhMask.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.4 (crypto/Ecdsa.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (crypto/Ecdsa.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.4 (crypto/Eip712.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (crypto/Eip712.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.4 (crypto/ElGamal.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (crypto/ElGamal.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.4 (multisig/ForwarderPrivate.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/ForwarderPrivate.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.4 (multisig/ForwarderShielded.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/ForwarderShielded.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -41,6 +41,11 @@ pragma language_version >= 0.26.0;
41
41
  * coins. Forwarding to a key no one controls strands every deposit
42
42
  * permanently. The zero-key guard rejects only the all-zero key, not an
43
43
  * otherwise-unspendable one.
44
+ *
45
+ * @notice The depositor must encrypt the forwarded output to the parent's
46
+ * encryption public key, obtained off-chain. The ledger cannot check that
47
+ * key against `_parent`, so a wrong or missing key leaves the coin
48
+ * spendable by the parent but invisible to its wallet.
44
49
  */
45
50
  module ForwarderShielded {
46
51
  import CompactStandardLibrary;
@@ -103,20 +108,24 @@ module ForwarderShielded {
103
108
  * `sendImmediateShielded` to the stored parent recipient (always the
104
109
  * `left` / coin-public-key arm today).
105
110
  *
106
- * @constraints k=15, rows=24373
111
+ * @constraints k=15, rows=23197
107
112
  *
108
113
  * Requirements:
109
114
  *
110
115
  * - Contract must be initialized.
116
+ * - The forwarded output must be encrypted to the parent's encryption
117
+ * public key (see the module header). If unsure, hand the returned
118
+ * `ShieldedSendResult` to the parent so it can recover the coin.
111
119
  *
112
120
  * @param {ShieldedCoinInfo} coin - The incoming shielded coin.
113
121
  *
114
- * @returns {[]} Empty tuple.
122
+ * @returns {ShieldedSendResult} The coin sent to the parent, with no change:
123
+ * the whole value is forwarded.
115
124
  */
116
- export circuit _deposit(coin: ShieldedCoinInfo): [] {
125
+ export circuit _deposit(coin: ShieldedCoinInfo): ShieldedSendResult {
117
126
  assertInitialized();
118
127
  receiveShielded(disclose(coin));
119
- sendImmediateShielded(disclose(coin), _parent, disclose(coin.value));
128
+ return sendImmediateShielded(disclose(coin), _parent, disclose(coin.value));
120
129
  }
121
130
 
122
131
  // ─── Init guards ────────────────────────────────────────────────
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.4 (multisig/ForwarderUnshielded.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/ForwarderUnshielded.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.4 (multisig/ProposalManager.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/ProposalManager.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -0,0 +1,275 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/Secp256k1EcdsaMultisig.compact)
3
+
4
+ pragma language_version >= 0.26.0;
5
+
6
+ /**
7
+ * @module Secp256k1EcdsaMultisig<#t>
8
+ * @description
9
+ * A `t`-of-`n` accountable multisignature over secp256k1 ECDSA: `n` signer
10
+ * commitments are registered at deploy, and a call passes when `t` distinct
11
+ * registered signers have signed the digest. The threshold `t` is the module
12
+ * generic, so it is also the approval count every gated call carries.
13
+ *
14
+ * @dev Notation:
15
+ * - `H(...)`: `persistentHash` (SHA-256), a `Bytes<32>` digest.
16
+ * - `cm = H(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`: a signer
17
+ * commitment, over the affine coordinates of the secp256k1 public key.
18
+ * - `n`: signers registered at `initialize`.
19
+ * - `t`: the threshold and the `assertApprovals` width.
20
+ *
21
+ * @dev Derivations:
22
+ * `H` hashes the `SignerCommitmentInput` struct in Compact's encoding, not a
23
+ * byte concatenation. Do not reimplement it: `calculateSignerId` is the
24
+ * exported pure circuit, and the consumer test suites pin its vectors. A
25
+ * wrong derivation registers commitments no key can satisfy.
26
+ *
27
+ * @dev Parameterization:
28
+ * `t` is fixed at import (`import "./Secp256k1EcdsaMultisig"<2> prefix
29
+ * Secp256k1EcdsaMultisig_;`) and `n` per `initialize<#n>` call. `Signer`
30
+ * enforces `n >= t` at `initialize`. No circuit takes a threshold argument.
31
+ * `t = 0` does not compile.
32
+ *
33
+ * @dev Compatibility:
34
+ * The wire format is the `Secp256k1EcdsaMultisig:signer` domain, the
35
+ * `SignerCommitmentInput` layout, and the salt. Changing any of them orphans
36
+ * every registered signer set. The digest is the consumer's and is not part
37
+ * of this module's format.
38
+ *
39
+ * @notice Privacy:
40
+ * - Public, per tx: the `t` commitments (`disclose(commitment)` in
41
+ * `Signer_assertSigner`), so WHICH signers approved.
42
+ * - Public, as state: `_signers`, `_signerCount`, `_threshold`,
43
+ * `_instanceSalt`. All exported.
44
+ * - Private: public keys and signatures, verified in-circuit and never
45
+ * disclosed; the digest, beyond what the consumer publishes.
46
+ * - Signer identity: keys sit behind `cm` with a public salt, so an observer
47
+ * holding a candidate key can test membership; one without cannot
48
+ * correlate a signer across deployments.
49
+ *
50
+ * @notice Security:
51
+ * - Order: `assertApprovals` runs `Signer_assertThresholdMet(t)` first, so an
52
+ * uninitialized registry fails before any signature work. Then pairwise
53
+ * distinct commitments, then per entry a registered commitment and a valid
54
+ * signature.
55
+ * - Low-s: verification goes through `Ecdsa_secp256k1EcdsaVerifyLowS`, so a
56
+ * high-s encoding is an invalid signature.
57
+ * - Distinct signers: all `t(t-1)/2` pairs are compared, so no key counts
58
+ * twice.
59
+ * - Bounds: `Signer` rejects `t > n` at `initialize`.
60
+ * - Once-only: `initialize` rejects a second call while signers are
61
+ * registered, and writes the `sealed` salt, so it is constructor-only.
62
+ * - Immutable to the deployed circuit set: no signer or threshold change is
63
+ * exported. Losing more than `n - t` keys freezes every gated circuit.
64
+ * - Digest binding is the consumer's: this module verifies signatures over
65
+ * whatever `msgHash` it is handed. Replay, domain and instance binding
66
+ * belong in the digest.
67
+ * - Atomicity: a failed assert rejects the whole tx before any state moves,
68
+ * and the sender pays no fee.
69
+ *
70
+ * @notice Concurrency:
71
+ * - Every read (`_threshold`, `_signers.member(cm)`, `_instanceSalt`) is
72
+ * state written only by `initialize`, so `assertApprovals` and the views
73
+ * commute with everything, including each other.
74
+ *
75
+ * @notice Scope:
76
+ * Registry and verification only: no digest construction, no nonce, no
77
+ * roles, no signer rotation. The consumer builds the digest and gates its
78
+ * circuits with `assertApprovals`.
79
+ *
80
+ * @notice Client-side duties:
81
+ * - Before deploy: pick a random `instanceSalt`, derive each `cm` with
82
+ * `calculateSignerId(pk, salt)`, pass them as `signerCommitments`.
83
+ * - Per call: collect exactly `t` low-s signatures from distinct signers over
84
+ * the consumer's digest, submit keys and signatures in matching `Vector<t>`
85
+ * positions.
86
+ *
87
+ * @dev Performance:
88
+ * - Cost is per entry: one ECDSA verify and one `H` for each of the `t`
89
+ * approvals, plus `t(t-1)/2` `Bytes<32>` compares.
90
+ * - `assertApprovals` (zkir-v3): `t = 1` is k=16, rows=40538; `t = 2` is
91
+ * k=17, rows=64908; `t = 3` is k=17, rows=94995.
92
+ *
93
+ * @dev Composition:
94
+ * - Import this module at exactly one width per contract. Two widths
95
+ * instantiate two `Secp256k1EcdsaMultisig` modules over one shared
96
+ * `Signer<Bytes<32>>` registry, so the second `initialize` trips the
97
+ * once-only guard and that width's `_instanceSalt` is never written.
98
+ * - Do not import `Signer` alongside this module. A direct import
99
+ * instantiates a separate registry that `assertApprovals` never reads.
100
+ */
101
+ module Secp256k1EcdsaMultisig<#t> {
102
+ import CompactStandardLibrary;
103
+ import "../crypto/Ecdsa" prefix Ecdsa_;
104
+ import "./Signer"<Bytes<32>> prefix Signer_;
105
+
106
+ // Named import/re-export of the registry state only, so a consuming
107
+ // contract's ledger keys read as `_signers` rather than `Signer__signers`.
108
+ // The circuits stay behind the `Signer_` prefix.
109
+ import { _signers, _signerCount, _threshold } from "./Signer"<Bytes<32>>;
110
+ export { _signers, _signerCount, _threshold };
111
+
112
+ /**
113
+ * @description Deployment-supplied salt for signer commitments. Set at
114
+ * initialization and never changed. The registered commitments must have been
115
+ * derived under this same salt.
116
+ */
117
+ export sealed ledger _instanceSalt: Bytes<32>;
118
+
119
+ /**
120
+ * @description Input to `persistentHash` for computing signer commitments.
121
+ */
122
+ struct SignerCommitmentInput {
123
+ pkX: Bytes<32>,
124
+ pkY: Bytes<32>,
125
+ salt: Bytes<32>,
126
+ domain: Bytes<32>
127
+ }
128
+
129
+ /**
130
+ * @description Stores the instance salt and registers the signer set at
131
+ * threshold `t`. Call from the consuming contract's constructor only.
132
+ * Writes the `sealed` salt, so it cannot be reached from an exported
133
+ * circuit. The set is final: signers must be known at deploy and no circuit
134
+ * adds, removes or replaces one afterwards.
135
+ *
136
+ * Requirements:
137
+ *
138
+ * - Can only be called once.
139
+ * - `instanceSalt` should be cryptographically random.
140
+ * - `signerCommitments` must not contain duplicates.
141
+ * - `n` must be at least `t`.
142
+ *
143
+ * @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
144
+ * @param {Vector<n, Bytes<32>>} signerCommitments - Hashed signer identities.
145
+ * @returns {[]} Empty tuple.
146
+ */
147
+ export circuit initialize<#n>(
148
+ instanceSalt: Bytes<32>,
149
+ signerCommitments: Vector<n, Bytes<32>>
150
+ ): [] {
151
+ assert(Signer_getSignerCount() == 0, "Secp256k1EcdsaMultisig: signers already registered");
152
+ _instanceSalt = disclose(instanceSalt);
153
+
154
+ for (const commitment of signerCommitments) {
155
+ Signer__addSigner(commitment);
156
+ }
157
+ Signer__changeThreshold(t);
158
+ }
159
+
160
+ /**
161
+ * @description Verifies `t` approvals over `msgHash`: distinct registered
162
+ * signers, each with a valid low-s signature.
163
+ *
164
+ * `pubkeys` and `signatures` are parallel: entry `i` of each belongs to the
165
+ * same signer.
166
+ *
167
+ * Requirements:
168
+ *
169
+ * - The module must be initialized.
170
+ * - No two entries may be the same signer.
171
+ * - Every public key must hash to a registered signer commitment.
172
+ * - Every signature must be valid over `msgHash` and use the low-s encoding.
173
+ *
174
+ * @param {Bytes<32>} msgHash - The digest the signers approved.
175
+ * @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
176
+ * @param {Vector<t, Secp256k1EcdsaSignature>} signatures - Signatures over `msgHash`.
177
+ * @returns {[]} Empty tuple.
178
+ */
179
+ export circuit assertApprovals(
180
+ msgHash: Bytes<32>,
181
+ pubkeys: Vector<t, Secp256k1Point>,
182
+ signatures: Vector<t, Secp256k1EcdsaSignature>
183
+ ): [] {
184
+ Signer_assertThresholdMet(t);
185
+
186
+ const commitments = map(signerCommitment, pubkeys);
187
+
188
+ for (const i of 0..t) {
189
+ for (const j of 0..t) {
190
+ if (i < j) {
191
+ assert(commitments[i] != commitments[j], "Secp256k1EcdsaMultisig: duplicate signer");
192
+ }
193
+ }
194
+ }
195
+
196
+ for (const i of 0..t) {
197
+ Signer_assertSigner(commitments[i]);
198
+ assert(
199
+ Ecdsa_secp256k1EcdsaVerifyLowS(msgHash, signatures[i], pubkeys[i]),
200
+ "Secp256k1EcdsaMultisig: invalid signature"
201
+ );
202
+ }
203
+ }
204
+
205
+ /**
206
+ * @description `map` callback. Commits `pk` under the instance salt.
207
+ *
208
+ * @param {Secp256k1Point} pk - The signer's secp256k1 public key.
209
+ * @returns {Bytes<32>} The signer commitment.
210
+ */
211
+ circuit signerCommitment(pk: Secp256k1Point): Bytes<32> {
212
+ return calculateSignerId(pk, _instanceSalt);
213
+ }
214
+
215
+ /**
216
+ * @description Computes a signer commitment from a secp256k1 public key,
217
+ * as `persistentHash(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`.
218
+ *
219
+ * Pure, so the deployer can derive constructor commitments off-chain the
220
+ * same way the circuits derive them in-circuit.
221
+ *
222
+ * Requirements:
223
+ *
224
+ * - `pk` must not be the secp256k1 identity point, which has no coordinates.
225
+ *
226
+ * @param {Secp256k1Point} pk - The secp256k1 public key.
227
+ * @param {Bytes<32>} salt - The instance salt.
228
+ * @returns {Bytes<32>} The signer commitment.
229
+ */
230
+ export pure circuit calculateSignerId(
231
+ pk: Secp256k1Point,
232
+ salt: Bytes<32>
233
+ ): Bytes<32> {
234
+ return persistentHash<SignerCommitmentInput>(SignerCommitmentInput {
235
+ pkX: secp256k1PointX(pk) as Bytes<32>,
236
+ pkY: secp256k1PointY(pk) as Bytes<32>,
237
+ salt: salt,
238
+ domain: pad(32, "Secp256k1EcdsaMultisig:signer")
239
+ });
240
+ }
241
+
242
+ /**
243
+ * @description Returns the number of registered signers.
244
+ *
245
+ * @constraints k=6, rows=34 (zkir-v3)
246
+ *
247
+ * @returns {Uint<8>} The signer count.
248
+ */
249
+ export circuit getSignerCount(): Uint<8> {
250
+ return Signer_getSignerCount();
251
+ }
252
+
253
+ /**
254
+ * @description Returns the approval threshold.
255
+ *
256
+ * @constraints k=6, rows=34 (zkir-v3)
257
+ *
258
+ * @returns {Uint<8>} The threshold.
259
+ */
260
+ export circuit getThreshold(): Uint<8> {
261
+ return Signer_getThreshold();
262
+ }
263
+
264
+ /**
265
+ * @description Returns whether the given commitment is a registered signer.
266
+ *
267
+ * @constraints k=8, rows=129 (zkir-v3)
268
+ *
269
+ * @param {Bytes<32>} commitment - The signer commitment to check.
270
+ * @returns {Boolean} True if registered.
271
+ */
272
+ export circuit isSigner(commitment: Bytes<32>): Boolean {
273
+ return Signer_isSigner(commitment);
274
+ }
275
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.4 (multisig/ShieldedTreasury.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/ShieldedTreasury.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.4 (multisig/ShieldedTreasuryStateless.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/ShieldedTreasuryStateless.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.4 (multisig/Signer.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/Signer.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.4 (multisig/UnshieldedTreasury.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/UnshieldedTreasury.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -90,8 +90,7 @@ module UnshieldedTreasury {
90
90
  // ─── Send ───────────────────────────────────────────────────────
91
91
 
92
92
  /**
93
- * @description Sends unshielded tokens from the treasury. Zero-amount sends
94
- * are permitted and are value-preserving no-ops.
93
+ * @description Sends unshielded tokens from the treasury.
95
94
  *
96
95
  * @warning Recipients are not validated. An unspendable recipient (such as a
97
96
  * zero address) is accepted and permanently removes the funds from the
@@ -111,6 +110,7 @@ module UnshieldedTreasury {
111
110
  *
112
111
  * Requirements:
113
112
  *
113
+ * - The amount must be non-zero. The ledger rejects zero-value outputs.
114
114
  * - The treasury must hold sufficient balance for the given token color.
115
115
  *
116
116
  * @param {Either<ContractAddress, UserAddress>} recipient - The recipient address.
@@ -124,6 +124,8 @@ module UnshieldedTreasury {
124
124
  color: Bytes<32>,
125
125
  amount: Uint<128>
126
126
  ): [] {
127
+ assert(amount != 0, "UnshieldedTreasury: zero amount");
128
+
127
129
  const bal = getTokenBalance(color);
128
130
  assert(bal >= amount, "UnshieldedTreasury: insufficient balance");
129
131
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.4 (multisig/examples/ForwarderPrivateExample.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/examples/ForwarderPrivateExample.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.4 (multisig/examples/ForwarderShieldedExample.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/examples/ForwarderShieldedExample.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -24,7 +24,7 @@ pragma language_version >= 0.26.0;
24
24
  import CompactStandardLibrary;
25
25
  import "../ForwarderShielded" prefix Forwarder_;
26
26
 
27
- export { ZswapCoinPublicKey, ContractAddress, ShieldedCoinInfo, Either };
27
+ export { ZswapCoinPublicKey, ContractAddress, ShieldedCoinInfo, ShieldedSendResult, Maybe, Either };
28
28
 
29
29
  /**
30
30
  * @description Deploys the forwarder bound to a specific parent
@@ -42,12 +42,15 @@ constructor(parent: ZswapCoinPublicKey) {
42
42
  * the configured parent. The coin is claimed via `receiveShielded` and
43
43
  * immediately re-sent via `sendImmediateShielded`.
44
44
  *
45
+ * The depositor must encrypt the forwarded output to the parent's
46
+ * encryption public key (see the `ForwarderShielded` header).
47
+ *
45
48
  * @param {ShieldedCoinInfo} coin - The incoming shielded coin.
46
49
  *
47
- * @returns {[]} Empty tuple.
50
+ * @returns {ShieldedSendResult} The coin sent to the parent, with no change.
48
51
  */
49
- export circuit deposit(coin: ShieldedCoinInfo): [] {
50
- Forwarder__deposit(coin);
52
+ export circuit deposit(coin: ShieldedCoinInfo): ShieldedSendResult {
53
+ return Forwarder__deposit(coin);
51
54
  }
52
55
 
53
56
  /**
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.4 (multisig/examples/ForwarderUnshieldedExample.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/examples/ForwarderUnshieldedExample.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.4 (multisig/examples/NativeShieldedTokenIssuerExample.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.6 (multisig/examples/NativeShieldedTokenIssuerExample.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -11,13 +11,15 @@ pragma language_version >= 0.26.0;
11
11
  * (`mintToSelf`), and leaves only through an ECDSA-authorized burn, of a
12
12
  * holder's coin (`burn`) or of one the contract holds (`burnFromSelf`).
13
13
  *
14
- * The token is non-transferable by construction. There is no transfer circuit,
15
- * so a holder's only exit is to pay the coin into a `burn` tx.
14
+ * Coins minted to holders are ordinary Zswap coins: holders transfer them
15
+ * through Zswap without the issuer's involvement and redeem by paying one
16
+ * into a `burn` tx. Only contract-held coins are locked. No send circuit
17
+ * exists, so they leave through `burnFromSelf` alone.
16
18
  */
17
19
 
18
20
  import CompactStandardLibrary;
19
21
 
20
- import "../presets/NativeShieldedTokenIssuer" prefix NativeShieldedTokenIssuer_;
22
+ import "../presets/NativeShieldedTokenIssuer"<2> prefix NativeShieldedTokenIssuer_;
21
23
 
22
24
  export { ZswapCoinPublicKey, ContractAddress, Either, Maybe };
23
25
  export { ShieldedCoinInfo, QualifiedShieldedCoinInfo };
@@ -37,7 +39,7 @@ import {
37
39
  _symbol,
38
40
  _decimals,
39
41
  _isInitialized,
40
- } from "../presets/NativeShieldedTokenIssuer";
42
+ } from "../presets/NativeShieldedTokenIssuer"<2>;
41
43
  export {
42
44
  _counter,
43
45
  _domainSeparator,
@@ -77,7 +79,7 @@ constructor(
77
79
  decimals_: Uint<8>,
78
80
  signerCommitments: Vector<3, Bytes<32>>,
79
81
  ) {
80
- NativeShieldedTokenIssuer_initialize(
82
+ NativeShieldedTokenIssuer_initialize<3>(
81
83
  instanceSalt,
82
84
  tokenDomain,
83
85
  name_,
@@ -113,7 +115,7 @@ export circuit mintToSelf(
113
115
 
114
116
  export circuit burn(
115
117
  coin: ShieldedCoinInfo,
116
- amount: Uint<64>,
118
+ amount: Uint<128>,
117
119
  refundTo: ZswapCoinPublicKey,
118
120
  pubkeys: Vector<2, Secp256k1Point>,
119
121
  signatures: Vector<2, Secp256k1EcdsaSignature>
@@ -123,7 +125,7 @@ export circuit burn(
123
125
 
124
126
  export circuit burnFromSelf(
125
127
  coin: QualifiedShieldedCoinInfo,
126
- amount: Uint<64>,
128
+ amount: Uint<128>,
127
129
  pubkeys: Vector<2, Secp256k1Point>,
128
130
  signatures: Vector<2, Secp256k1EcdsaSignature>
129
131
  ): Maybe<ShieldedCoinInfo> {