@openzeppelin/compact-contracts 0.3.0-rc.1 → 0.4.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +14 -4
  2. package/access/AccessControl.compact +14 -14
  3. package/access/Ownable.compact +16 -10
  4. package/access/ShieldedAccessControl.compact +26 -2
  5. package/access/ZOwnablePK.compact +9 -9
  6. package/crypto/EcdhMask.compact +5 -5
  7. package/crypto/Ecdsa.compact +111 -0
  8. package/crypto/Eip712.compact +168 -0
  9. package/crypto/ElGamal.compact +33 -107
  10. package/multisig/EcdsaSignerManager.compact +246 -0
  11. package/multisig/ForwarderPrivate.compact +12 -6
  12. package/multisig/ForwarderShielded.compact +3 -3
  13. package/multisig/ForwarderUnshielded.compact +3 -3
  14. package/multisig/ProposalManager.compact +195 -23
  15. package/multisig/ShieldedTreasury.compact +29 -2
  16. package/multisig/ShieldedTreasuryStateless.compact +2 -2
  17. package/multisig/Signer.compact +28 -80
  18. package/multisig/UnshieldedTreasury.compact +10 -2
  19. package/multisig/{presets/forwarder/ForwarderPrivate.compact → examples/ForwarderPrivateExample.compact} +4 -4
  20. package/multisig/{presets/forwarder/ForwarderShielded.compact → examples/ForwarderShieldedExample.compact} +4 -4
  21. package/multisig/{presets/forwarder/ForwarderUnshielded.compact → examples/ForwarderUnshieldedExample.compact} +4 -4
  22. package/multisig/examples/NativeShieldedTokenIssuerExample.compact +143 -0
  23. package/multisig/examples/ShieldedMultiSigV2Example.compact +77 -0
  24. package/multisig/presets/NativeShieldedTokenIssuer.compact +542 -0
  25. package/multisig/presets/ShieldedMultiSigV2.compact +267 -259
  26. package/package.json +31 -25
  27. package/security/Allowlist.compact +6 -6
  28. package/security/Blocklist.compact +6 -6
  29. package/security/Initializable.compact +9 -5
  30. package/security/Pausable.compact +11 -7
  31. package/token/ConfidentialFungibleToken.compact +143 -270
  32. package/token/FungibleToken.compact +36 -22
  33. package/token/MultiToken.compact +16 -16
  34. package/token/NativeShieldedToken.compact +17 -66
  35. package/token/NativeShieldedTokenCore.compact +26 -48
  36. package/token/NativeShieldedTokenFamily.compact +17 -67
  37. package/token/NonFungibleToken.compact +40 -79
  38. package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +12 -7
  39. package/token/extensions/NativeShieldedTokenDerivedNonce.compact +3 -3
  40. package/token/extensions/NativeShieldedTokenFamilyPublicSupply.compact +8 -8
  41. package/token/extensions/NativeShieldedTokenPublicSupply.compact +8 -8
  42. package/token/extensions/NativeShieldedTokenPublicSupplyCore.compact +6 -6
  43. package/utils/EvmAbi.compact +134 -0
  44. package/utils/Utils.compact +2 -12
  45. package/multisig/presets/ShieldedMultiSig.compact +0 -227
  46. package/multisig/presets/ShieldedMultiSigV3.compact +0 -368
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-rc.1 (multisig/presets/ShieldedMultiSigV2.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.2 (multisig/presets/ShieldedMultiSigV2.compact)
3
3
 
4
- pragma language_version >= 0.23.0;
4
+ pragma language_version >= 0.26.0;
5
5
 
6
6
  /**
7
- * @title ShieldedMultisigV2
7
+ * @module ShieldedMultiSigV2
8
8
  * @description Privacy-preserving 2-of-3 multisig contract.
9
9
  *
10
10
  * Signer identities are stored as commitments: hashes of ECDSA public
@@ -18,263 +18,271 @@ pragma language_version >= 0.23.0;
18
18
  * Deposits call receiveShielded only. The operator discovers coin indices
19
19
  * through ZswapOutput events from the indexer, constructs QualifiedShieldedCoinInfo
20
20
  * off-chain, and provides it as a circuit parameter for spending.
21
- */
22
-
23
- import CompactStandardLibrary;
24
-
25
- import "../ProposalManager" prefix Proposal_;
26
- import "../ShieldedTreasuryStateless" prefix Treasury_;
27
- import "../Signer"<Bytes<32>> prefix Signer_;
28
-
29
- // ─── Types ──────────────────────────────────────────────────────
30
-
31
- /**
32
- * @description Accumulator for fold-based signature verification.
33
- * Threads the valid count, previous commitment (for duplicate
34
- * detection), and message hash through each iteration.
35
- */
36
- export struct VerificationState {
37
- validCount: Uint<8>,
38
- prevCommitment: Bytes<32>,
39
- msgHash: Bytes<32>
40
- }
41
-
42
- /**
43
- * @description Input to persistentHash for computing signer commitments.
44
- * Combines the ECDSA public key with an instance-specific salt and
45
- * domain separator to produce a unique, unlinkable commitment.
46
- */
47
- export struct SignerCommitmentInput {
48
- pk: Bytes<64>,
49
- salt: Bytes<32>,
50
- domain: Bytes<32>
51
- }
52
-
53
- // ─── State ──────────────────────────────────────────────────────
54
-
55
- ledger _nonce: Counter;
56
- ledger _instanceSalt: Bytes<32>;
57
-
58
- // ─── Constructor ────────────────────────────────────────────────
59
-
60
- /**
61
- * @description Deploys the multisig with 3 signer commitments and
62
- * a threshold.
63
- *
64
- * Each commitment is computed off-chain as:
65
- * persistentHash(SignerCommitmentInput { pk, instanceSalt, domain })
66
- * where domain is pad(32, "MultiSig:signer:").
67
- *
68
- * The instanceSalt should be a random value to prevent the same public
69
- * key from producing the same commitment across different multisig
70
- * deployments, breaking cross-contract signer correlation.
71
- *
72
- * Requirements:
73
- *
74
- * - `thresh` must be > 0 and <= 2 (matches the 2-signature `execute` surface).
75
- * - `signerCommitments` must not contain duplicates.
76
- * - `instanceSalt` should be cryptographically random.
77
- *
78
- * @param {Bytes<32>} instanceSalt - Random salt for commitment derivation.
79
- * @param {Vector<3, Bytes<32>>} signerCommitments - Hashed signer identities.
80
- * @param {Uint<8>} thresh - Minimum approvals required.
81
- */
82
- constructor(
83
- instanceSalt: Bytes<32>,
84
- signerCommitments: Vector<3, Bytes<32>>,
85
- thresh: Uint<8>,
86
- ) {
87
- assert(
88
- thresh <= 2,
89
- "ShieldedMultiSigV2: threshold cannot exceed 2 (execute verifies at most 2 signatures)"
90
- );
91
- _instanceSalt = disclose(instanceSalt);
92
- Signer_initialize<3>(signerCommitments, thresh);
93
- }
94
-
95
- // ─── Deposit ────────────────────────────────────────────────────
96
-
97
- /**
98
- * @description Receives a shielded coin into the multisig treasury.
99
- *
100
- * No access control which allows anyone to deposit. The coin is claimed at the
101
- * protocol level through receiveShielded. No coin data is stored on the
102
- * public ledger, preserving full balance privacy.
103
- *
104
- * The operator discovers the coin's Merkle tree index by subscribing
105
- * to ZswapOutput events via the indexer, filtering by contract address,
106
- * and extracting mt_index. Combined with the known ShieldedCoinInfo,
107
- * this produces the QualifiedShieldedCoinInfo needed for spending.
108
- *
109
- * @param {ShieldedCoinInfo} coin - The incoming shielded coin.
110
- */
111
- export circuit deposit(coin: ShieldedCoinInfo): [] {
112
- receiveShielded(disclose(coin));
113
- }
114
-
115
- // ─── Execute ────────────────────────────────────────────────────
116
-
117
- /**
118
- * @description Executes a shielded send authorized by threshold signatures.
119
- *
120
- * The circuit reads the current nonce from the ledger, increments it,
121
- * then reconstructs the message hash that signers must have signed
122
- * off-chain: `persistentHash(nonce, recipient address, coin color, amount)`.
123
- *
124
- * Signatures are verified via fold over parallel pubkey and signature
125
- * vectors. Each public key is hashed with the instance salt to produce
126
- * a commitment, checked against the signer registry, and the signature
127
- * is verified against the message hash. Duplicate signers are rejected
128
- * via inequality check on adjacent commitments.
129
- *
130
- * @notice ECDSA verification is stubbed. Replace stubVerifySignature
131
- * with ecdsaVerify when Compact ECDSA primitives are available.
132
- *
133
- * @notice Duplicate detection via != only works for exactly 2 signers.
134
- * Production contracts with larger signer sets need a different
135
- * uniqueness enforcement mechanism.
136
21
  *
137
- * Requirements:
22
+ * @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
138
23
  *
139
- * - Both public keys must hash to registered signer commitments.
140
- * - Both signatures must be valid over the message hash.
141
- * - Signers must not be duplicates.
142
- * - Coin value must be >= amount.
143
- *
144
- * @param {Proposal_Recipient} to - The recipient.
145
- * @param {Uint<128>} amount - The amount to send.
146
- * @param {QualifiedShieldedCoinInfo} coin - The coin to spend (from operator's pool).
147
- * @param {Vector<2, Bytes<64>>} pubkeys - ECDSA public keys of approving signers.
148
- * @param {Vector<2, Bytes<64>>} signatures - ECDSA signatures over the operation.
149
- *
150
- * @returns {ShieldedSendResult} The send result including any change.
151
- */
152
- export circuit execute(
153
- to: Proposal_Recipient,
154
- amount: Uint<128>,
155
- coin: QualifiedShieldedCoinInfo,
156
- pubkeys: Vector<2, Bytes<64>>,
157
- signatures: Vector<2, Bytes<64>>
158
- ): ShieldedSendResult {
159
- // Increment nonce
160
- const currentNonce = _nonce;
161
- _nonce.increment(1);
162
-
163
- // Construct message hash
164
- const msgHash = persistentHash<Vector<4, Bytes<32>>>([
165
- currentNonce as Bytes<32>,
166
- to.address,
167
- coin.color,
168
- amount as Bytes<32>
169
- ]);
170
-
171
- // Verify signatures via fold over parallel vectors
172
- const initialState = VerificationState {
173
- validCount: 0 as Uint<8>,
174
- prevCommitment: pad(32, ""),
175
- msgHash: msgHash
176
- };
177
-
178
- const finalState = fold(verifySignature, initialState, pubkeys, signatures);
179
- Signer_assertThresholdMet(finalState.validCount);
180
-
181
- // Execute transfer
182
- const normalizedRecipient = Proposal_toShieldedRecipient(to);
183
- return Treasury__send(coin, normalizedRecipient, amount);
184
- }
185
-
186
- // ─── Signature Verification ─────────────────────────────────────
187
-
188
- /**
189
- * @description Fold callback. Verifies one signer's approval.
190
- *
191
- * Computes the signer's commitment from their public key and the
192
- * instance salt, checks for duplicates against the previous commitment,
193
- * verifies registry membership, and validates the ECDSA signature.
194
- *
195
- * @param {VerificationState} state - Accumulator threaded through fold.
196
- * @param {Bytes<64>} pubkey - The signer's ECDSA public key.
197
- * @param {Bytes<64>} signature - The signer's signature over msgHash.
198
- *
199
- * @returns {VerificationState} Updated accumulator.
24
+ * @notice Implementing contracts register the signer set from their
25
+ * constructor by calling this module's `initialize`, and MUST NOT import
26
+ * `EcdsaSignerManager` directly.
200
27
  */
201
- circuit verifySignature(
202
- state: VerificationState,
203
- pubkey: Bytes<64>,
204
- signature: Bytes<64>
205
- ): VerificationState {
206
- const commitment = _calculateSignerId(pubkey, _instanceSalt);
207
-
208
- // Duplicate detection — sufficient for 2 signers only
209
- assert(commitment != state.prevCommitment, "Multisig: duplicate signer");
210
-
211
- // Verify this commitment is a registered signer
212
- Signer_assertSigner(commitment);
213
-
214
- // TODO: Replace with actual ECDSA primitive when available
215
- // assert(ecdsaVerify(pubkey, state.msgHash, signature), "Multisig: invalid signature");
216
- assert(stubVerifySignature(pubkey, state.msgHash, signature), "Multisig: invalid signature");
217
-
218
- return VerificationState {
219
- validCount: state.validCount + 1 as Uint<8>,
220
- prevCommitment: commitment,
221
- msgHash: state.msgHash
222
- };
223
- }
224
-
225
- /**
226
- * @description Computes a signer commitment from an ECDSA public key.
227
- *
228
- * The commitment is persistentHash(pk, salt, domain) where:
229
- * - pk: the signer's ECDSA public key (64 bytes)
230
- * - salt: instance-specific random value (prevents cross-contract correlation)
231
- * - domain: "MultiSig:signer:" (domain separation)
232
- *
233
- * This is a pure circuit. It can be called off-chain by the deployer
234
- * to compute commitments for the constructor.
235
- *
236
- * @param {Bytes<64>} pk - The ECDSA public key.
237
- * @param {Bytes<32>} salt - The instance salt.
238
- *
239
- * @returns {Bytes<32>} The signer commitment.
240
- */
241
- export pure circuit _calculateSignerId(
242
- pk: Bytes<64>,
243
- salt: Bytes<32>
244
- ): Bytes<32> {
245
- return persistentHash<SignerCommitmentInput>(SignerCommitmentInput {
246
- pk: pk,
247
- salt: salt,
248
- domain: pad(32, "MultiSig:signer:")
249
- });
250
- }
251
-
252
- /**
253
- * @description Stub for ECDSA signature verification.
254
- * Always returns true. MUST be replaced before any non-test deployment.
255
- */
256
- circuit stubVerifySignature(
257
- pubkey: Bytes<64>,
258
- msgHash: Bytes<32>,
259
- signature: Bytes<64>
260
- ): Boolean {
261
- return true;
262
- }
263
-
264
- // ─── View ───────────────────────────────────────────────────────
265
-
266
- export circuit getNonce(): Uint<64> {
267
- return _nonce;
268
- }
269
-
270
- export circuit getSignerCount(): Uint<8> {
271
- return Signer_getSignerCount();
272
- }
273
-
274
- export circuit getThreshold(): Uint<8> {
275
- return Signer_getThreshold();
276
- }
277
-
278
- export circuit isSigner(commitment: Bytes<32>): Boolean {
279
- return Signer_isSigner(commitment);
28
+ module ShieldedMultiSigV2 {
29
+ import CompactStandardLibrary;
30
+
31
+ import "../EcdsaSignerManager" prefix EcdsaSignerManager_;
32
+ import "../../crypto/Eip712" prefix Eip712_;
33
+ import "../../utils/EvmAbi" prefix EvmAbi_;
34
+ import "../ProposalManager" prefix Proposal_;
35
+ import "../ShieldedTreasuryStateless" prefix Treasury_;
36
+
37
+ // Implementing contracts need this for the re-exported `execute` signature.
38
+ // Importing `ProposalManager` again instead would give them a second,
39
+ // unwritten copy of its state.
40
+ export { Proposal_Recipient };
41
+
42
+ // ─── State ──────────────────────────────────────────────────────
43
+
44
+ /**
45
+ * @description Monotonic operation counter. `execute` reads it as that
46
+ * operation's nonce and then increments, so no two executions share one.
47
+ */
48
+ ledger _nonce: Counter;
49
+
50
+ /**
51
+ * @description The EIP-712 domain separator every `execute` digest is built
52
+ * under, derived from `(name, version, instanceSalt)`. Set once at
53
+ * construction.
54
+ */
55
+ export sealed ledger _domainSeparator: Bytes<32>;
56
+
57
+ // ─── Initialization ─────────────────────────────────────────────
58
+
59
+ /**
60
+ * @description Registers the signer set and threshold, and fixes the
61
+ * EIP-712 domain separator.
62
+ *
63
+ * @notice Call from the implementing contract's constructor only. It writes
64
+ * the `sealed` `_domainSeparator`, so implementing contracts MUST NOT expose
65
+ * `initialize` as a post-deploy circuit.
66
+ *
67
+ * The EIP-712 domain fixed here is `(name, version, salt)`. `chainId` and
68
+ * `verifyingContract` are absent: no network identifier is available
69
+ * in-circuit, and a 32-byte Midnight address does not fit
70
+ * `verifyingContract`'s `address` type.
71
+ *
72
+ * Replay protection comes from `execute` binding `kernel.self()` in its
73
+ * struct rather than from the domain. The ledger randomizes a contract's
74
+ * address at deployment, so no two deployments share one on any network.
75
+ * A signature for one cannot authorize another.
76
+ *
77
+ * `instanceSalt` also keys the signer commitments, so a fresh value per
78
+ * deployment keeps one public key from producing the same commitment in two
79
+ * deployments which would link the signer set across them.
80
+ *
81
+ * Requirements:
82
+ *
83
+ * - `instanceSalt` should be cryptographically random.
84
+ * - `signerCommitments` must not contain duplicates.
85
+ * - `thresh` must be non-zero and must not exceed 2.
86
+ *
87
+ * @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 - Minimum approvals required.
90
+ * @returns {[]} Empty tuple.
91
+ */
92
+ export circuit initialize(
93
+ instanceSalt: Bytes<32>,
94
+ signerCommitments: Vector<3, Bytes<32>>,
95
+ thresh: Uint<8>
96
+ ): [] {
97
+ EcdsaSignerManager_initialize<3>(instanceSalt, signerCommitments, thresh);
98
+
99
+ const hashedName = keccak256<Bytes<18>>(pad(18, "ShieldedMultiSigV2"));
100
+ const hashedVersion = keccak256<Bytes<1>>(pad(1, "1"));
101
+ _domainSeparator = Eip712_domainSeparator(
102
+ hashedName,
103
+ hashedVersion,
104
+ disclose(instanceSalt)
105
+ );
106
+ }
107
+
108
+ /**
109
+ * @description `keccak256` of the `Execute` type string:
110
+ * `Execute(bytes32 contractAddress,uint256 nonce,uint8 recipientKind,bytes32 recipient,bytes32 coinColor,uint256 amount)`
111
+ *
112
+ * @dev Written as a literal word. `compactc` emits a full keccak
113
+ * permutation even for a constant argument, so hashing the type string
114
+ * in-circuit would add ~4.2k rows to every `execute`. The spec checks this
115
+ * word against the type string above.
116
+ *
117
+ * @returns {Bytes<32>} The `Execute` type hash.
118
+ */
119
+ pure circuit EXECUTE_TYPEHASH(): Bytes<32> {
120
+ const typeHash: Vector<32, Uint<8>> = [
121
+ 0x8e, 0x36, 0xc9, 0x78, 0xf2, 0x00, 0x15, 0xe8,
122
+ 0x1d, 0x7a, 0x53, 0xef, 0xc3, 0x2e, 0x2b, 0x08,
123
+ 0xf1, 0x24, 0x97, 0xa5, 0x05, 0x7c, 0x70, 0x20,
124
+ 0x7f, 0x8b, 0xc4, 0xb7, 0xc6, 0xcf, 0x5e, 0x15
125
+ ];
126
+
127
+ return typeHash as Bytes<32>;
128
+ }
129
+
130
+ // ─── Deposit ────────────────────────────────────────────────────
131
+
132
+ /**
133
+ * @description Receives a shielded coin into the multisig treasury.
134
+ *
135
+ * No access control which allows anyone to deposit. The coin is claimed at the
136
+ * protocol level through receiveShielded. No coin data is stored on the
137
+ * public ledger, preserving full balance privacy.
138
+ *
139
+ * The operator discovers the coin's Merkle tree index by subscribing
140
+ * to ZswapOutput events via the indexer, filtering by contract address,
141
+ * and extracting mt_index. Combined with the known ShieldedCoinInfo,
142
+ * this produces the QualifiedShieldedCoinInfo needed for spending.
143
+ *
144
+ * @constraints k=13, rows=5898 (zkir-v3)
145
+ *
146
+ * @param {ShieldedCoinInfo} coin - The incoming shielded coin.
147
+ */
148
+ export circuit deposit(coin: ShieldedCoinInfo): [] {
149
+ receiveShielded(disclose(coin));
150
+ }
151
+
152
+ // ─── Execute ────────────────────────────────────────────────────
153
+
154
+ /**
155
+ * @description Executes a shielded send authorized by threshold signatures.
156
+ *
157
+ * The circuit reads the current nonce from the ledger, increments it,
158
+ * then reconstructs the EIP-712 digest that signers must have signed
159
+ * off-chain: the `Execute` struct (contract address, nonce, recipient kind,
160
+ * recipient, coin color, amount) hashed under this deployment's domain
161
+ * separator. The digest is bound to the contract instance and the full recipient (kind and
162
+ * address), so a signature authorizes exactly one operation on one instance.
163
+ *
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.
167
+ *
168
+ * @constraints k=17, rows=104644 (zkir-v3)
169
+ *
170
+ * Requirements:
171
+ *
172
+ * - Both public keys must hash to registered signer commitments.
173
+ * - Both signatures must be valid over the message hash and use the low-s
174
+ * encoding.
175
+ * - Signers must not be duplicates.
176
+ * - Coin value must be >= amount.
177
+ *
178
+ * @param {Proposal_Recipient} to - The recipient.
179
+ * @param {Uint<128>} amount - The amount to send.
180
+ * @param {QualifiedShieldedCoinInfo} coin - The coin to spend (from operator's pool).
181
+ * @param {Vector<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
182
+ * @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the operation.
183
+ *
184
+ * @returns {ShieldedSendResult} The send result including any change.
185
+ */
186
+ export circuit execute(
187
+ to: Proposal_Recipient,
188
+ amount: Uint<128>,
189
+ coin: QualifiedShieldedCoinInfo,
190
+ pubkeys: Vector<2, Secp256k1Point>,
191
+ signatures: Vector<2, Secp256k1EcdsaSignature>
192
+ ): ShieldedSendResult {
193
+ // Increment nonce
194
+ const currentNonce = _nonce;
195
+ _nonce.increment(1);
196
+
197
+ // Construct message hash
198
+ const msgHash = Eip712_hashTypedData(_domainSeparator, keccak256<Vector<7, Bytes<32>>>([
199
+ EXECUTE_TYPEHASH(),
200
+ // Binds the digest to this deployment. The domain cannot carry the
201
+ // address, so removing this word removes cross-instance replay
202
+ // protection.
203
+ kernel.self().bytes,
204
+ EvmAbi_uint64Word(currentNonce),
205
+ EvmAbi_uint8Word(to.kind as Uint<8>),
206
+ to.address,
207
+ coin.color,
208
+ EvmAbi_uint128Word(amount)
209
+ ]));
210
+
211
+ EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
212
+
213
+ // Execute transfer
214
+ const normalizedRecipient = Proposal_toShieldedRecipient(to);
215
+ return Treasury__send(coin, normalizedRecipient, amount);
216
+ }
217
+
218
+ // ─── Commitments ────────────────────────────────────────────────
219
+
220
+ /**
221
+ * @description Computes a signer commitment from a secp256k1 public key,
222
+ * as `persistentHash(pkX, pkY, salt, "multisig:signer:")`.
223
+ *
224
+ * Pure, so the deployer can derive constructor commitments off-chain.
225
+ *
226
+ * Requirements:
227
+ *
228
+ * - `pk` must not be the secp256k1 identity point, which has no coordinates.
229
+ *
230
+ * @param {Secp256k1Point} pk - The secp256k1 public key.
231
+ * @param {Bytes<32>} salt - The instance salt.
232
+ *
233
+ * @returns {Bytes<32>} The signer commitment.
234
+ */
235
+ export pure circuit _calculateSignerId(
236
+ pk: Secp256k1Point,
237
+ salt: Bytes<32>
238
+ ): Bytes<32> {
239
+ return EcdsaSignerManager_calculateSignerId(pk, salt);
240
+ }
241
+
242
+ // ─── View ───────────────────────────────────────────────────────
243
+
244
+ /**
245
+ * @description Returns the nonce the next `execute` digest will bind to.
246
+ *
247
+ * @constraints k=6, rows=34 (zkir-v3)
248
+ *
249
+ * @returns {Uint<64>} The current nonce.
250
+ */
251
+ export circuit getNonce(): Uint<64> {
252
+ return _nonce;
253
+ }
254
+
255
+ /**
256
+ * @description Returns the number of registered signers.
257
+ *
258
+ * @constraints k=6, rows=34 (zkir-v3)
259
+ *
260
+ * @returns {Uint<8>} The signer count.
261
+ */
262
+ export circuit getSignerCount(): Uint<8> {
263
+ return EcdsaSignerManager_getSignerCount();
264
+ }
265
+
266
+ /**
267
+ * @description Returns the approval threshold.
268
+ *
269
+ * @constraints k=6, rows=34 (zkir-v3)
270
+ *
271
+ * @returns {Uint<8>} The threshold.
272
+ */
273
+ export circuit getThreshold(): Uint<8> {
274
+ return EcdsaSignerManager_getThreshold();
275
+ }
276
+
277
+ /**
278
+ * @description Returns whether the given commitment is a registered signer.
279
+ *
280
+ * @constraints k=8, rows=129 (zkir-v3)
281
+ *
282
+ * @param {Bytes<32>} commitment - The signer commitment to check.
283
+ * @returns {Boolean} True if registered.
284
+ */
285
+ export circuit isSigner(commitment: Bytes<32>): Boolean {
286
+ return EcdsaSignerManager_isSigner(commitment);
287
+ }
280
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/compact-contracts",
3
- "version": "0.3.0-rc.1",
3
+ "version": "0.4.0-alpha.2",
4
4
  "description": "OpenZeppelin Compact contract library",
5
5
  "keywords": [
6
6
  "openzeppelin",
@@ -24,49 +24,55 @@
24
24
  "#test-utils/*": "./test-utils/*"
25
25
  },
26
26
  "scripts": {
27
- "compile": "compact-compiler --exclude '*/archive/*'",
27
+ "compile": "compact-compiler --exclude '*/archive/*' --exclude '*/crypto/*' --exclude '*/multisig/*'",
28
28
  "compile:access": "compact-compiler --dir access",
29
29
  "compile:archive": "compact-compiler --dir archive",
30
- "compile:crypto": "compact-compiler --dir crypto",
31
- "compile:multisig": "compact-compiler --dir multisig",
30
+ "compile:crypto": "compact-compiler --dir crypto --feature-zkir-v3 --exclude 'MockElGamal.compact' --exclude 'MockEcdhMask.compact' --exclude 'MockCurveOps.compact' && compact-compiler --dir crypto/test/mocks --exclude 'MockEcdsa.compact' --exclude 'MockEip712.compact'",
31
+ "compile:multisig": "compact-compiler --dir multisig --feature-zkir-v3",
32
32
  "compile:security": "compact-compiler --dir security",
33
33
  "compile:token": "compact-compiler --dir token",
34
34
  "compile:utils": "compact-compiler --dir utils",
35
- "compile:integration": "SKIP_ZK=true compact-compiler --src test/integration/_mocks",
36
- "build": "compact-builder --hierarchical --out dist --clean-dist --exclude '*/archive/*' --exclude 'Mock*' --exclude '*.mock.compact' --copy package.json --copy ../README.md && find dist -type d -empty -delete",
35
+ "compile:integration": "compact-compiler --src test/integration/_mocks",
36
+ "build": "compact-builder --hierarchical --out dist --clean-dist --feature-zkir-v3 --exclude '*/archive/*' --exclude 'Mock*' --exclude '*.mock.compact' --copy package.json --copy ../README.md && find dist -type d -empty -delete",
37
37
  "test": "vitest run --project unit",
38
38
  "test:coverage": "vitest run --project unit --coverage",
39
39
  "test:integration": "vitest run --project integration",
40
40
  "test:harness": "vitest run --project harness",
41
41
  "test:harness:live": "MIDNIGHT_BACKEND=live vitest run --project harness-live",
42
+ "test:scripts": "vitest run --project scripts",
42
43
  "types": "tsc -p tsconfig.json --noEmit",
43
44
  "clean": "git clean -fXd"
44
45
  },
45
46
  "engines": {
46
- "node": ">=22"
47
+ "node": ">=24"
47
48
  },
48
49
  "dependencies": {
49
- "@openzeppelin/compact-cli": "^0.0.2"
50
+ "@openzeppelin/compact-cli": "^0.1.1"
50
51
  },
51
52
  "devDependencies": {
52
- "@midnight-ntwrk/compact-js": "2.5.1",
53
- "@midnight-ntwrk/compact-runtime": "0.16.0",
54
- "@midnight-ntwrk/ledger-v8": "8.1.0",
55
- "@midnight-ntwrk/midnight-js-contracts": "4.1.1",
56
- "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.1.1",
57
- "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.1.1",
58
- "@midnight-ntwrk/midnight-js-network-id": "4.1.1",
59
- "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.1.1",
60
- "@midnight-ntwrk/midnight-js-protocol": "4.1.1",
61
- "@midnight-ntwrk/midnight-js-types": "4.1.1",
62
- "@midnight-ntwrk/testkit-js": "4.1.1",
63
- "@openzeppelin/compact-simulator": "^0.3.1",
64
- "@tsconfig/node24": "^24.0.4",
65
- "@types/node": "26.1.2",
66
- "@vitest/coverage-v8": "^4.1.10",
67
- "fast-check": "^4.9.0",
53
+ "@midnight-ntwrk/compact-js": "2.5.5-rc.8",
54
+ "@midnight-ntwrk/compact-runtime": "0.19.0",
55
+ "@midnight-ntwrk/midnight-js-contracts": "5.0.0-beta.7",
56
+ "@midnight-ntwrk/midnight-js-http-client-proof-provider": "5.0.0-beta.7",
57
+ "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "5.0.0-beta.7",
58
+ "@midnight-ntwrk/midnight-js-network-id": "5.0.0-beta.7",
59
+ "@midnight-ntwrk/midnight-js-node-zk-config-provider": "5.0.0-beta.7",
60
+ "@midnight-ntwrk/midnight-js-protocol": "5.0.0-beta.7",
61
+ "@midnight-ntwrk/midnight-js-types": "5.0.0-beta.7",
62
+ "@midnight-ntwrk/testkit-js": "5.0.0-beta.7",
63
+ "@midnightntwrk/ledger-v9": "1.0.0-rc.5",
64
+ "@midnightntwrk/wallet-sdk-address-format": "4.0.0-beta.2",
65
+ "@noble/curves": "^2.2.0",
66
+ "@noble/hashes": "^2.2.0",
67
+ "@openzeppelin/compact-simulator": "^0.4.0",
68
+ "@tsconfig/node24": "^24.0.5",
69
+ "@types/node": "26.5.1",
70
+ "@vitest/coverage-v8": "^5.0.0",
71
+ "ethers": "^6.17.0",
72
+ "fast-check": "^4.10.1",
68
73
  "ts-node": "^10.9.2",
69
74
  "typescript": "^6.0.3",
70
- "vitest": "^4.1.10"
75
+ "vite": "^8.3.0",
76
+ "vitest": "^5.0.0"
71
77
  }
72
78
  }