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

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 (45) hide show
  1. package/README.md +1 -1
  2. package/access/AccessControl.compact +1 -1
  3. package/access/Ownable.compact +1 -3
  4. package/access/ShieldedAccessControl.compact +44 -41
  5. package/access/ZOwnablePK.compact +12 -5
  6. package/crypto/EcdhMask.compact +11 -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 +17 -6
  11. package/multisig/ForwarderShielded.compact +1 -1
  12. package/multisig/ForwarderUnshielded.compact +1 -1
  13. package/multisig/ProposalManager.compact +65 -127
  14. package/multisig/RecipientTypes.compact +48 -0
  15. package/multisig/Secp256k1EcdsaMultisig.compact +275 -0
  16. package/multisig/ShieldedTreasury.compact +20 -8
  17. package/multisig/ShieldedTreasuryStateless.compact +11 -5
  18. package/multisig/Signer.compact +1 -1
  19. package/multisig/UnshieldedTreasury.compact +24 -11
  20. package/multisig/examples/ForwarderPrivateExample.compact +3 -2
  21. package/multisig/examples/ForwarderShieldedExample.compact +1 -1
  22. package/multisig/examples/ForwarderUnshieldedExample.compact +1 -1
  23. package/multisig/examples/NativeShieldedTokenIssuerExample.compact +4 -4
  24. package/multisig/examples/ShieldedMultiSigV2Example.compact +7 -9
  25. package/multisig/presets/NativeShieldedTokenIssuer.compact +88 -84
  26. package/multisig/presets/ShieldedMultiSigV2.compact +50 -52
  27. package/package.json +1 -1
  28. package/security/Allowlist.compact +1 -1
  29. package/security/Blocklist.compact +1 -1
  30. package/security/Initializable.compact +1 -3
  31. package/security/Pausable.compact +1 -1
  32. package/token/ConfidentialFungibleToken.compact +70 -22
  33. package/token/FungibleToken.compact +1 -3
  34. package/token/MultiToken.compact +1 -3
  35. package/token/NativeShieldedToken.compact +11 -3
  36. package/token/NativeShieldedTokenCore.compact +1 -1
  37. package/token/NativeShieldedTokenFamily.compact +1 -1
  38. package/token/NonFungibleToken.compact +1 -3
  39. package/token/examples/ZOwnableNativeShieldedTokenExample.compact +109 -0
  40. package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
  41. package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
  42. package/token/presets/ZOwnableNativeShieldedToken.compact +314 -0
  43. package/utils/EvmAbi.compact +1 -1
  44. package/utils/Utils.compact +1 -1
  45. package/multisig/EcdsaSignerManager.compact +0 -248
@@ -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 (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,32 +14,41 @@ 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
20
26
  * off-chain, and provides it as a circuit parameter for spending.
21
27
  *
28
+ * @notice Keeping coin data off the ledger hides balances from the public,
29
+ * not from a depositor. Whoever knows a deposited coin can find the `execute`
30
+ * that spends it, recover the amount sent, test candidate recipients against
31
+ * the sent coin's commitment, and derive the change coin, so every later
32
+ * spend descending from that change and the balance it carries are traceable
33
+ * by that depositor. See `ShieldedTreasuryStateless` for the derivation.
34
+ *
22
35
  * @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
23
36
  *
24
37
  * @notice Implementing contracts register the signer set from their
25
38
  * constructor by calling this module's `initialize`, and MUST NOT import
26
- * `EcdsaSignerManager` directly.
39
+ * `Signer` or `Secp256k1EcdsaMultisig` directly.
27
40
  */
28
- module ShieldedMultiSigV2 {
41
+ module ShieldedMultiSigV2<#t> {
29
42
  import CompactStandardLibrary;
30
43
 
31
- import "../EcdsaSignerManager" prefix EcdsaSignerManager_;
44
+ import "../Secp256k1EcdsaMultisig"<t> prefix Secp256k1EcdsaMultisig_;
32
45
  import "../../crypto/Eip712" prefix Eip712_;
33
46
  import "../../utils/EvmAbi" prefix EvmAbi_;
34
- import "../ProposalManager" prefix Proposal_;
47
+ import "../RecipientTypes";
35
48
  import "../ShieldedTreasuryStateless" prefix Treasury_;
36
49
 
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 ──────────────────────────────────────────────────────
50
+ // Implementing contracts need these for the re-exported `execute` signature.
51
+ export { Recipient, RecipientKind };
43
52
 
44
53
  /**
45
54
  * @description Monotonic operation counter. `execute` reads it as that
@@ -54,10 +63,8 @@ module ShieldedMultiSigV2 {
54
63
  */
55
64
  export sealed ledger _domainSeparator: Bytes<32>;
56
65
 
57
- // ─── Initialization ─────────────────────────────────────────────
58
-
59
66
  /**
60
- * @description Registers the signer set and threshold, and fixes the
67
+ * @description Registers the signer set at threshold `t` and fixes the
61
68
  * EIP-712 domain separator.
62
69
  *
63
70
  * @notice Call from the implementing contract's constructor only. It writes
@@ -82,19 +89,17 @@ module ShieldedMultiSigV2 {
82
89
  *
83
90
  * - `instanceSalt` should be cryptographically random.
84
91
  * - `signerCommitments` must not contain duplicates.
85
- * - `thresh` must be 2, the number of signatures `execute` verifies.
92
+ * - `n` must be at least `t`.
86
93
  *
87
94
  * @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.
95
+ * @param {Vector<n, Bytes<32>>} signerCommitments - Hashed signer identities.
90
96
  * @returns {[]} Empty tuple.
91
97
  */
92
- export circuit initialize(
98
+ export circuit initialize<#n>(
93
99
  instanceSalt: Bytes<32>,
94
- signerCommitments: Vector<3, Bytes<32>>,
95
- thresh: Uint<8>
100
+ signerCommitments: Vector<n, Bytes<32>>
96
101
  ): [] {
97
- EcdsaSignerManager_initialize<3>(instanceSalt, signerCommitments, thresh);
102
+ Secp256k1EcdsaMultisig_initialize<n>(instanceSalt, signerCommitments);
98
103
 
99
104
  const hashedName = keccak256<Bytes<18>>(pad(18, "ShieldedMultiSigV2"));
100
105
  const hashedVersion = keccak256<Bytes<1>>(pad(1, "1"));
@@ -127,14 +132,13 @@ module ShieldedMultiSigV2 {
127
132
  return typeHash as Bytes<32>;
128
133
  }
129
134
 
130
- // ─── Deposit ────────────────────────────────────────────────────
131
-
132
135
  /**
133
136
  * @description Receives a shielded coin into the multisig treasury.
134
137
  *
135
138
  * No access control which allows anyone to deposit. The coin is claimed at the
136
139
  * protocol level through receiveShielded. No coin data is stored on the
137
- * public ledger, preserving full balance privacy.
140
+ * public ledger, so no balance is readable from it; a depositor can still
141
+ * follow the coin they deposited (see the module notice).
138
142
  *
139
143
  * The operator discovers the coin's Merkle tree index by subscribing
140
144
  * to ZswapOutput events via the indexer, filtering by contract address,
@@ -149,8 +153,6 @@ module ShieldedMultiSigV2 {
149
153
  receiveShielded(disclose(coin));
150
154
  }
151
155
 
152
- // ─── Execute ────────────────────────────────────────────────────
153
-
154
156
  /**
155
157
  * @description Executes a shielded send authorized by threshold signatures.
156
158
  *
@@ -161,38 +163,38 @@ module ShieldedMultiSigV2 {
161
163
  * separator. The digest is bound to the contract instance and the full recipient (kind and
162
164
  * address), so a signature authorizes exactly one operation on one instance.
163
165
  *
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.
166
+ * Approvals are checked by `Secp256k1EcdsaMultisig`, which derives each
167
+ * signer's commitment from their public key and the instance salt and
168
+ * rejects duplicates. Constraints below are measured at `t = 2`.
167
169
  *
168
170
  * @notice `to` must be a `ShieldedUser`. A contract-addressed coin would be
169
171
  * unclaimable, making the transaction malformed.
170
172
  *
171
- * @constraints k=17, rows=98977 (zkir-v3)
173
+ * @constraints k=17, rows=98969 (zkir-v3)
172
174
  *
173
175
  * Requirements:
174
176
  *
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
177
+ * - No two entries may be the same signer.
178
+ * - Every public key must hash to a registered signer commitment.
179
+ * - Every signature must be valid over the message hash and use the low-s
177
180
  * encoding.
178
- * - Signers must not be duplicates.
179
181
  * - `to.kind` must be `ShieldedUser`.
180
182
  * - Coin value must be >= amount.
181
183
  *
182
- * @param {Proposal_Recipient} to - The recipient.
184
+ * @param {Recipient} to - The recipient.
183
185
  * @param {Uint<128>} amount - The amount to send.
184
186
  * @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.
187
+ * @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
188
+ * @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the operation.
187
189
  *
188
190
  * @returns {ShieldedSendResult} The send result including any change.
189
191
  */
190
192
  export circuit execute(
191
- to: Proposal_Recipient,
193
+ to: Recipient,
192
194
  amount: Uint<128>,
193
195
  coin: QualifiedShieldedCoinInfo,
194
- pubkeys: Vector<2, Secp256k1Point>,
195
- signatures: Vector<2, Secp256k1EcdsaSignature>
196
+ pubkeys: Vector<t, Secp256k1Point>,
197
+ signatures: Vector<t, Secp256k1EcdsaSignature>
196
198
  ): ShieldedSendResult {
197
199
  // Increment nonce
198
200
  const currentNonce = _nonce;
@@ -212,11 +214,11 @@ module ShieldedMultiSigV2 {
212
214
  EvmAbi_uint128Word(amount)
213
215
  ]));
214
216
 
215
- EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
217
+ Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
216
218
 
217
219
  assert(
218
- to.kind == Proposal_RecipientKind.ShieldedUser,
219
- "Multisig: recipient must be a coin public key"
220
+ to.kind == RecipientKind.ShieldedUser,
221
+ "ShieldedMultiSigV2: recipient must be a coin public key"
220
222
  );
221
223
  const recipient = left<ZswapCoinPublicKey, ContractAddress>(
222
224
  ZswapCoinPublicKey { bytes: to.address }
@@ -224,11 +226,9 @@ module ShieldedMultiSigV2 {
224
226
  return Treasury__send(coin, recipient, amount);
225
227
  }
226
228
 
227
- // ─── Commitments ────────────────────────────────────────────────
228
-
229
229
  /**
230
230
  * @description Computes a signer commitment from a secp256k1 public key,
231
- * as `persistentHash(pkX, pkY, salt, "multisig:signer:")`.
231
+ * as `persistentHash(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`.
232
232
  *
233
233
  * Pure, so the deployer can derive constructor commitments off-chain.
234
234
  *
@@ -245,11 +245,9 @@ module ShieldedMultiSigV2 {
245
245
  pk: Secp256k1Point,
246
246
  salt: Bytes<32>
247
247
  ): Bytes<32> {
248
- return EcdsaSignerManager_calculateSignerId(pk, salt);
248
+ return Secp256k1EcdsaMultisig_calculateSignerId(pk, salt);
249
249
  }
250
250
 
251
- // ─── View ───────────────────────────────────────────────────────
252
-
253
251
  /**
254
252
  * @description Returns the nonce the next `execute` digest will bind to.
255
253
  *
@@ -269,7 +267,7 @@ module ShieldedMultiSigV2 {
269
267
  * @returns {Uint<8>} The signer count.
270
268
  */
271
269
  export circuit getSignerCount(): Uint<8> {
272
- return EcdsaSignerManager_getSignerCount();
270
+ return Secp256k1EcdsaMultisig_getSignerCount();
273
271
  }
274
272
 
275
273
  /**
@@ -280,7 +278,7 @@ module ShieldedMultiSigV2 {
280
278
  * @returns {Uint<8>} The threshold.
281
279
  */
282
280
  export circuit getThreshold(): Uint<8> {
283
- return EcdsaSignerManager_getThreshold();
281
+ return Secp256k1EcdsaMultisig_getThreshold();
284
282
  }
285
283
 
286
284
  /**
@@ -292,6 +290,6 @@ module ShieldedMultiSigV2 {
292
290
  * @returns {Boolean} True if registered.
293
291
  */
294
292
  export circuit isSigner(commitment: Bytes<32>): Boolean {
295
- return EcdsaSignerManager_isSigner(commitment);
293
+ return Secp256k1EcdsaMultisig_isSigner(commitment);
296
294
  }
297
295
  }
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",
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 (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 (security/Blocklist.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/Initializable.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (security/Initializable.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -35,8 +35,6 @@ module Initializable {
35
35
  /**
36
36
  * @description Initializes the state thus ensuring the calling circuit can only be called once.
37
37
  *
38
- * @constraints k=6, rows=28
39
- *
40
38
  * Requirements:
41
39
  *
42
40
  * - Contract must not be initialized.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (security/Pausable.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (security/Pausable.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 (token/ConfidentialFungibleToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/ConfidentialFungibleToken.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -28,6 +28,16 @@ pragma language_version >= 0.26.0;
28
28
  * (`extensions/ConfidentialFungibleTokenPublicSupply`) a composing contract pairs
29
29
  * with them if it wants a tracked supply.
30
30
  *
31
+ * @notice Allowances. `approve` funds the allowance at approve time: `value`
32
+ * moves out of the caller's spendable balance into the escrow, where it stays
33
+ * until spent or re-approved. Funding is required, not a choice: the spender
34
+ * holds no owner encryption key, so at spend time it can only prove against
35
+ * its own escrow copy, never that the owner's balance covers the allowance.
36
+ * An approve conserves the owner's holdings (spendable plus every outstanding
37
+ * escrow), so allowances across spenders can sum to the whole holding but
38
+ * never exceed it, and a re-approve replaces the prior one rather than
39
+ * stacking.
40
+ *
31
41
  * @notice Single-receiver. Auditor support, freeze, and admin-style seize are
32
42
  * deliberately left to companion modules or a future multi-receiver variant.
33
43
  * The escrow-spend primitive here (`_spendEscrow`) enables user-consenting
@@ -35,6 +45,9 @@ pragma language_version >= 0.26.0;
35
45
  * requires authority visibility into balances and is therefore out of scope
36
46
  * for the single-receiver base.
37
47
  *
48
+ * @dev Initialization. Every exported circuit except `initialize` asserts the
49
+ * contract is initialized.
50
+ *
38
51
  * @dev Trust model for witnesses.
39
52
  *
40
53
  * Witness implementations live in the user's wallet (TypeScript frontend) and
@@ -390,7 +403,9 @@ module ConfidentialFungibleToken {
390
403
  /**
391
404
  * @description Escrowed allowances, keyed by owner then spender. Membership is
392
405
  * checked independently of the entry, so an allowance that was never escrowed
393
- * is unspendable.
406
+ * is unspendable. A spend also requires both owner and spender to be
407
+ * registered, so an entry written here for an unregistered account (for
408
+ * example by a composing contract) is unspendable as well.
394
409
  * @type {Bytes<32>} owner - The account that escrowed the allowance.
395
410
  * @type {Bytes<32>} spender - The account allowed to spend it.
396
411
  * @type {EscrowEntry} entry - The escrowed allowance.
@@ -618,25 +633,37 @@ module ConfidentialFungibleToken {
618
633
 
619
634
  /**
620
635
  * @description Returns the escrow entry (the encrypted allowance) that `owner`
621
- * granted `spender`. When no allowance exists, returns a well-formed
622
- * encrypted-zero entry (its encrypted amounts decrypt to 0, analogous to an
623
- * ERC-20 zero allowance) rather than `default<EscrowEntry>`: the returned
624
- * Jubjub points are on-curve, so the view is safe to prove (e.g. through a
625
- * contract-to-contract call) and safe for a wallet to decrypt, whereas a zeroed
626
- * default holds off-curve points. The cap is encrypted, not public.
636
+ * granted `spender`. When no allowance exists, returns a placeholder entry
637
+ * rather than `default<EscrowEntry>`: the returned Jubjub points are on-curve,
638
+ * so the view is safe to prove (e.g. through a contract-to-contract call),
639
+ * whereas a zeroed default holds off-curve points. The cap is encrypted, not
640
+ * public.
641
+ *
642
+ * @notice The placeholder's `spenderCt` and `ownerCt` are genuine encryptions
643
+ * of 0. Its `ownerMemo` is not: it is a sentinel for an absent allowance,
644
+ * recognised by `ephemeralPk == identity`, which `EcdhMask_encrypt` never
645
+ * produces and `EcdhMask_decrypt` rejects. A wallet must test for the
646
+ * sentinel rather than decrypt and narrow it.
647
+ *
648
+ * @notice "No allowance" and "an allowance of 0" both read as encrypted-zero in
649
+ * the amount fields; the memo sentinel tells them apart, since `approve` stores
650
+ * a real memo even for 0. This getter grants nothing, the spend path
651
+ * (`transferFrom`/`burnFrom`) gates on `_escrow` membership independently, so a
652
+ * non-existent allowance is unspendable.
653
+ *
654
+ * @constraints k=10, rows=938
627
655
  *
628
- * @notice "No allowance" and "an allowance of 0" both read as encrypted-zero;
629
- * a caller that must distinguish them reads `_escrow` membership off-chain. This
630
- * getter grants nothing, the spend path (`transferFrom`/`burnFrom`) gates on
631
- * `_escrow` membership independently, so a non-existent allowance is unspendable.
656
+ * Requirements:
632
657
  *
633
- * @constraints k=10, rows=936
658
+ * - Contract is initialized.
634
659
  *
635
660
  * @param {Bytes<32>} owner - The account that granted the allowance.
636
661
  * @param {Bytes<32>} spender - The approved spender.
637
- * @return {EscrowEntry} - The encrypted escrow entry (encrypted-zero if none).
662
+ * @return {EscrowEntry} - The encrypted escrow entry (a placeholder with the
663
+ * absent-allowance sentinel if none).
638
664
  */
639
665
  export circuit allowance(owner: Bytes<32>, spender: Bytes<32>): EscrowEntry {
666
+ assertInitialized();
640
667
  if (!_escrow.member(disclose(owner)) ||
641
668
  !_escrow.lookup(disclose(owner)).member(disclose(spender))) {
642
669
  const id = constructJubjubPoint(0 as Field, 1 as Field);
@@ -656,12 +683,17 @@ module ConfidentialFungibleToken {
656
683
  /**
657
684
  * @description Returns whether `account` has registered an encryption key.
658
685
  *
659
- * @constraints k=9, rows=303
686
+ * @constraints k=9, rows=305
687
+ *
688
+ * Requirements:
689
+ *
690
+ * - Contract is initialized.
660
691
  *
661
692
  * @param {Bytes<32>} account - The account to query.
662
693
  * @return {Boolean} - True if `account` is registered.
663
694
  */
664
695
  export circuit isRegistered(account: Bytes<32>): Boolean {
696
+ assertInitialized();
665
697
  return _encryptionKeys.member(disclose(account));
666
698
  }
667
699
 
@@ -812,6 +844,12 @@ module ConfidentialFungibleToken {
812
844
  * `sum(balances) == totalSupply` invariant. Pair it with a debit (as `transfer`
813
845
  * does) or a supply increase (as a supply layer's `mint` does).
814
846
  *
847
+ * Requirements:
848
+ *
849
+ * - Contract is initialized.
850
+ * - `value` is within `MAX_TRANSFER_VALUE()`.
851
+ * - `account` is non-zero and registered.
852
+ *
815
853
  * @param account - The recipient account id.
816
854
  * @param value - The amount to credit.
817
855
  */
@@ -928,7 +966,7 @@ module ConfidentialFungibleToken {
928
966
  * credits `to`, and pushes an encrypted memo to `to`. The amount is hidden;
929
967
  * the (sender, recipient) pair is public.
930
968
  *
931
- * @constraints k=16, rows=41623
969
+ * @constraints k=16, rows=41621
932
970
  *
933
971
  * @notice Returns the caller's (sender's) `accountId` for caller-side gating.
934
972
  * The sender is derived from the same authentication `_debit` performs, so the
@@ -946,7 +984,6 @@ module ConfidentialFungibleToken {
946
984
  * @return {Bytes<32>} - The caller's (sender's) accountId.
947
985
  */
948
986
  export circuit transfer(to: Bytes<32>, value: Uint<128>): Bytes<32> {
949
- assertInitialized();
950
987
  // Derive the sender from the same authentication `_debit` uses, so the
951
988
  // returned id is exactly the account whose funds moved.
952
989
  const senderId = _debit(value);
@@ -966,7 +1003,7 @@ module ConfidentialFungibleToken {
966
1003
  * operations (`mint`/`burn`) live in a separate layer outside this conserving
967
1004
  * surface, and only they may break conservation.
968
1005
  *
969
- * @constraints k=16, rows=41612
1006
+ * @constraints k=16, rows=41610
970
1007
  *
971
1008
  * @notice Like `transfer`, the credit lands in the recipient's pending pool
972
1009
  * (see the dual-balance note); the caller sweeps their own incoming value.
@@ -992,7 +1029,6 @@ module ConfidentialFungibleToken {
992
1029
  * @return {Bytes<32>} - The caller's accountId (for caller-side gating).
993
1030
  */
994
1031
  export circuit _move(to: Bytes<32>, value: Uint<128>): Bytes<32> {
995
- assertInitialized();
996
1032
  const senderId = _debit(value);
997
1033
  _credit(to, value);
998
1034
  return senderId;
@@ -1125,7 +1161,7 @@ module ConfidentialFungibleToken {
1125
1161
  * caller via `approve`) and credits it to `to`, reducing both escrow copies by
1126
1162
  * `value`. The amount is hidden.
1127
1163
  *
1128
- * ???? @constraints k=17, rows=68920
1164
+ * @constraints k=17, rows=68922
1129
1165
  *
1130
1166
  * @notice Returns the caller's (spender's) `accountId` for caller-side gating.
1131
1167
  *
@@ -1135,6 +1171,7 @@ module ConfidentialFungibleToken {
1135
1171
  * - `value` is within `MAX_TRANSFER_VALUE()`.
1136
1172
  * - `to` is registered and is not the zero account.
1137
1173
  * - An escrow from `fromAddress` to the caller exists with at least `value`.
1174
+ * - `fromAddress` and the caller are registered.
1138
1175
  *
1139
1176
  * @param {Bytes<32>} fromAddress - The owner whose escrow is drawn on.
1140
1177
  * @param {Bytes<32>} to - The recipient account.
@@ -1146,7 +1183,6 @@ module ConfidentialFungibleToken {
1146
1183
  to: Bytes<32>,
1147
1184
  value: Uint<128>
1148
1185
  ): Bytes<32> {
1149
- assertInitialized();
1150
1186
  _assertValueInBound(value);
1151
1187
  assert(to != default<Bytes<32>>,
1152
1188
  "ConfidentialFungibleToken: invalid receiver");
@@ -1299,6 +1335,13 @@ module ConfidentialFungibleToken {
1299
1335
  * not prove the post-refund balance on a later re-approve/revoke. See the
1300
1336
  * `EscrowEntry` field doc.
1301
1337
  *
1338
+ * Requirements:
1339
+ *
1340
+ * - Contract is initialized.
1341
+ * - `value` is within `MAX_TRANSFER_VALUE()`.
1342
+ * - An escrow from `fromAddress` to the caller exists with at least `value`.
1343
+ * - `fromAddress` and the caller are registered.
1344
+ *
1302
1345
  * @param fromAddress - The owner whose escrow the caller (spender) draws on.
1303
1346
  * @param value - The amount to consume from the allowance.
1304
1347
  */
@@ -1310,6 +1353,10 @@ module ConfidentialFungibleToken {
1310
1353
  assert(_escrow.member(disclose(fromAddress)) &&
1311
1354
  _escrow.lookup(disclose(fromAddress)).member(disclose(spenderId)),
1312
1355
  "ConfidentialFungibleToken: no escrow");
1356
+ assert(_encryptionKeys.member(disclose(spenderId)),
1357
+ "ConfidentialFungibleToken: spender not registered");
1358
+ assert(_encryptionKeys.member(disclose(fromAddress)),
1359
+ "ConfidentialFungibleToken: owner not registered");
1313
1360
 
1314
1361
  const entry = _escrow.lookup(disclose(fromAddress)).lookup(disclose(spenderId));
1315
1362
 
@@ -1374,13 +1421,14 @@ module ConfidentialFungibleToken {
1374
1421
  * recipient credit. The building block for a composing contract's
1375
1422
  * `burnFrom`; it performs no supply accounting (see `_burn`).
1376
1423
  *
1377
- * ???? @constraints k=16, rows=42201
1424
+ * @constraints k=16, rows=42205
1378
1425
  *
1379
1426
  * Requirements:
1380
1427
  *
1381
1428
  * - Contract is initialized.
1382
1429
  * - `value` is within `MAX_TRANSFER_VALUE()`.
1383
1430
  * - An escrow from `fromAddress` to the caller exists with at least `value`.
1431
+ * - `fromAddress` and the caller are registered.
1384
1432
  *
1385
1433
  * @param {Bytes<32>} fromAddress - The owner whose escrow is consumed.
1386
1434
  * @param {Uint<128>} value - The amount to burn.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (token/FungibleToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/FungibleToken.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -130,8 +130,6 @@ module FungibleToken {
130
130
  * @dev This MUST be called in the implementing contract's constructor. Failure to do so
131
131
  * can lead to an irreparable contract.
132
132
  *
133
- * @constraints k=10, rows=71
134
- *
135
133
  * @param {Opaque<"string">} name_ - The name of the token.
136
134
  * @param {Opaque<"string">} symbol_ - The symbol of the token.
137
135
  * @param {Uint<8>} decimals_ - The number of decimals used to get the user representation.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (token/MultiToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/MultiToken.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -176,8 +176,6 @@ module MultiToken {
176
176
  /**
177
177
  * @description Initializes the contract by setting the base URI for all tokens.
178
178
  *
179
- * @constraints k=6, rows=31
180
- *
181
179
  * Requirements:
182
180
  *
183
181
  * - Must be called in the contract's constructor.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (token/NativeShieldedToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/NativeShieldedToken.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -230,7 +230,13 @@ module NativeShieldedToken {
230
230
  *
231
231
  * @notice The consumer SHOULD persist the returned coin info in its own
232
232
  * ledger state: it is the contract's holding and is not otherwise
233
- * recoverable.
233
+ * recoverable. The coin is already claimed as a receive by the mint, so a
234
+ * custody module must record it without calling `receiveShielded` on it
235
+ * again (see the recipient-arm note in `NativeShieldedTokenCore`).
236
+ * `ShieldedTreasury._deposit` claims on the way in and is not that path;
237
+ * a record-only circuit is tracked in compact-contracts#1112. Until a
238
+ * custody module is composed, the only spend of a contract-held coin is
239
+ * `_burnFromSelf`.
234
240
  *
235
241
  * @constraints k=14, rows=10221
236
242
  *
@@ -310,7 +316,9 @@ module NativeShieldedToken {
310
316
  *
311
317
  * @dev The consumer SHOULD persist the returned change coin info in its own
312
318
  * ledger state: the change replaces `coin` as the contract's holding, and
313
- * its info is not otherwise recoverable.
319
+ * its info is not otherwise recoverable. Like a `_mintToSelf` coin, the
320
+ * change is already claimed as a receive, so a custody module must record
321
+ * it without a second `receiveShielded`.
314
322
  *
315
323
  * @constraints k=15, rows=23208
316
324
  *
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.5 (token/NativeShieldedTokenCore.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/NativeShieldedTokenCore.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 (token/NativeShieldedTokenFamily.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/NativeShieldedTokenFamily.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 (token/NonFungibleToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0 (token/NonFungibleToken.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -165,8 +165,6 @@ module NonFungibleToken {
165
165
  * This MUST be called in the implementing contract's constructor.
166
166
  * Failure to do so can lead to an irreparable contract.
167
167
  *
168
- * @constraints k=6, rows=52
169
- *
170
168
  * Requirements:
171
169
  *
172
170
  * - Contract is not initialized.