@openzeppelin/compact-contracts 0.1.0 → 0.3.0-alpha

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 (33) hide show
  1. package/README.md +15 -16
  2. package/access/AccessControl.compact +3 -35
  3. package/access/Ownable.compact +47 -60
  4. package/access/ShieldedAccessControl.compact +43 -10
  5. package/access/ZOwnablePK.compact +45 -10
  6. package/crypto/ElGamal.compact +473 -0
  7. package/multisig/ForwarderPrivate.compact +229 -0
  8. package/multisig/ForwarderShielded.compact +149 -0
  9. package/multisig/ForwarderUnshielded.compact +152 -0
  10. package/multisig/ProposalManager.compact +328 -0
  11. package/multisig/ShieldedTreasury.compact +201 -0
  12. package/multisig/ShieldedTreasuryStateless.compact +76 -0
  13. package/multisig/Signer.compact +312 -0
  14. package/multisig/SignerManager.compact +205 -0
  15. package/multisig/UnshieldedTreasury.compact +114 -0
  16. package/multisig/presets/ShieldedMultiSig.compact +236 -0
  17. package/multisig/presets/ShieldedMultiSigV2.compact +280 -0
  18. package/multisig/presets/ShieldedMultiSigV3.compact +368 -0
  19. package/multisig/presets/forwarder/ForwarderPrivate.compact +125 -0
  20. package/multisig/presets/forwarder/ForwarderShielded.compact +62 -0
  21. package/multisig/presets/forwarder/ForwarderUnshielded.compact +64 -0
  22. package/package.json +12 -6
  23. package/security/Allowlist.compact +101 -0
  24. package/security/Blocklist.compact +99 -0
  25. package/security/Initializable.compact +18 -2
  26. package/security/Pausable.compact +2 -2
  27. package/token/FungibleToken.compact +79 -103
  28. package/token/MultiToken.compact +67 -93
  29. package/token/NativeShieldedToken.compact +285 -0
  30. package/token/NativeShieldedTokenCore.compact +343 -0
  31. package/token/NativeShieldedTokenFamily.compact +287 -0
  32. package/token/NonFungibleToken.compact +85 -110
  33. package/utils/Utils.compact +86 -2
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.1.0 (token/NonFungibleToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-alpha (token/NonFungibleToken.compact)
3
3
 
4
- pragma language_version >= 0.21.0;
4
+ pragma language_version >= 0.23.0;
5
5
 
6
6
  /**
7
7
  * @module NonFungibleToken
@@ -83,13 +83,19 @@ pragma language_version >= 0.21.0;
83
83
 
84
84
  module NonFungibleToken {
85
85
  import CompactStandardLibrary;
86
- import "../security/Initializable" prefix Initializable_;
87
86
  import "../utils/Utils" prefix Utils_;
88
87
 
89
88
  /// Public state
90
89
  export sealed ledger _name: Opaque<"string">;
91
90
  export sealed ledger _symbol: Opaque<"string">;
92
91
 
92
+ /**
93
+ * @description Initialization flag, tracked per-module to avoid the compiler's
94
+ * shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
95
+ * Initializable module for rationale.
96
+ */
97
+ export ledger _isInitialized: Boolean;
98
+
93
99
  /**
94
100
  * @description Mapping from token IDs to their owner addresses.
95
101
  * @type {Uint<128>} tokenId - The unique identifier for a token.
@@ -148,18 +154,6 @@ module NonFungibleToken {
148
154
  */
149
155
  witness wit_NonFungibleTokenSK(): Bytes<32>;
150
156
 
151
- /**
152
- * @description Returns a canonical zero Either value (left variant with zero Bytes<32>).
153
- * Used as the zero value for mint/burn operations and cleared approvals.
154
- *
155
- * @return {Either<Bytes<32>, ContractAddress>} - The zero value.
156
- */
157
- export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
158
- return Either<Bytes<32>, ContractAddress> {
159
- is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
160
- };
161
- }
162
-
163
157
  /**
164
158
  * @description Initializes the contract by setting the name and symbol.
165
159
  *
@@ -177,11 +171,38 @@ module NonFungibleToken {
177
171
  * @return {[]} - Empty tuple.
178
172
  */
179
173
  export circuit initialize(name_: Opaque<"string">, symbol_: Opaque<"string">): [] {
180
- Initializable_initialize();
174
+ assertNotInitialized();
175
+ _isInitialized = true;
181
176
  _name = disclose(name_);
182
177
  _symbol = disclose(symbol_);
183
178
  }
184
179
 
180
+ /**
181
+ * @description Asserts that the contract has been initialized, throwing an error if not.
182
+ *
183
+ * Requirements:
184
+ *
185
+ * - Contract must be initialized.
186
+ *
187
+ * @return {[]} - Empty tuple.
188
+ */
189
+ circuit assertInitialized(): [] {
190
+ assert(_isInitialized, "NonFungibleToken: contract not initialized");
191
+ }
192
+
193
+ /**
194
+ * @description Asserts that the contract has not been initialized, throwing an error if it has.
195
+ *
196
+ * Requirements:
197
+ *
198
+ * - Contract must not be initialized.
199
+ *
200
+ * @return {[]} - Empty tuple.
201
+ */
202
+ circuit assertNotInitialized(): [] {
203
+ assert(!_isInitialized, "NonFungibleToken: contract already initialized");
204
+ }
205
+
185
206
  /**
186
207
  * @description Returns the number of tokens in `owner`'s account.
187
208
  *
@@ -195,7 +216,7 @@ module NonFungibleToken {
195
216
  * @return {Uint<128>} - The number of tokens in `owner`'s account.
196
217
  */
197
218
  export circuit balanceOf(owner: Either<Bytes<32>, ContractAddress>): Uint<128> {
198
- Initializable_assertInitialized();
219
+ assertInitialized();
199
220
  const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
200
221
 
201
222
  if (!_balances.member(disclose(canonOwner))) {
@@ -219,7 +240,7 @@ module NonFungibleToken {
219
240
  * @return {Either<Bytes<32>, ContractAddress>} - The account that owns the token.
220
241
  */
221
242
  export circuit ownerOf(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
222
- Initializable_assertInitialized();
243
+ assertInitialized();
223
244
  return _requireOwned(tokenId);
224
245
  }
225
246
 
@@ -235,7 +256,7 @@ module NonFungibleToken {
235
256
  * @return {Opaque<"string">} - The token name.
236
257
  */
237
258
  export circuit name(): Opaque<"string"> {
238
- Initializable_assertInitialized();
259
+ assertInitialized();
239
260
  return _name;
240
261
  }
241
262
 
@@ -251,7 +272,7 @@ module NonFungibleToken {
251
272
  * @return {Opaque<"string">} - The token symbol.
252
273
  */
253
274
  export circuit symbol(): Opaque<"string"> {
254
- Initializable_assertInitialized();
275
+ assertInitialized();
255
276
  return _symbol;
256
277
  }
257
278
 
@@ -275,7 +296,7 @@ module NonFungibleToken {
275
296
  * @return {Opaque<"string">} - the token id's URI.
276
297
  */
277
298
  export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
278
- Initializable_assertInitialized();
299
+ assertInitialized();
279
300
  _requireOwned(tokenId);
280
301
 
281
302
  if (!_tokenURIs.member(disclose(tokenId))) {
@@ -302,7 +323,7 @@ module NonFungibleToken {
302
323
  * @return {[]} - Empty tuple.
303
324
  */
304
325
  export circuit _setTokenURI(tokenId: Uint<128>, tokenURI: Opaque<"string">): [] {
305
- Initializable_assertInitialized();
326
+ assertInitialized();
306
327
  _requireOwned(tokenId);
307
328
 
308
329
  return _tokenURIs.insert(disclose(tokenId), disclose(tokenURI));
@@ -330,7 +351,7 @@ module NonFungibleToken {
330
351
  * @return {[]} - Empty tuple.
331
352
  */
332
353
  export circuit approve(to: Either<Bytes<32>, ContractAddress>, tokenId: Uint<128>): [] {
333
- Initializable_assertInitialized();
354
+ assertInitialized();
334
355
  const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
335
356
  _approve(to, tokenId, auth);
336
357
  }
@@ -349,7 +370,7 @@ module NonFungibleToken {
349
370
  * @return {Either<Bytes<32>, ContractAddress>} - The account approved to manage the token
350
371
  */
351
372
  export circuit getApproved(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
352
- Initializable_assertInitialized();
373
+ assertInitialized();
353
374
  _requireOwned(tokenId);
354
375
 
355
376
  return _getApproved(tokenId);
@@ -377,7 +398,7 @@ module NonFungibleToken {
377
398
  operator: Either<Bytes<32>, ContractAddress>,
378
399
  approved: Boolean
379
400
  ): [] {
380
- Initializable_assertInitialized();
401
+ assertInitialized();
381
402
  const owner = left<Bytes<32>, ContractAddress>(_computeAccountId());
382
403
  _setApprovalForAll(owner, operator, approved);
383
404
  }
@@ -399,7 +420,7 @@ module NonFungibleToken {
399
420
  owner: Either<Bytes<32>, ContractAddress>,
400
421
  operator: Either<Bytes<32>, ContractAddress>
401
422
  ): Boolean {
402
- Initializable_assertInitialized();
423
+ assertInitialized();
403
424
  const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
404
425
  const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
405
426
 
@@ -439,7 +460,7 @@ module NonFungibleToken {
439
460
  to: Either<Bytes<32>, ContractAddress>,
440
461
  tokenId: Uint<128>
441
462
  ): [] {
442
- Initializable_assertInitialized();
463
+ assertInitialized();
443
464
  const isContractAddr = !to.is_left;
444
465
  assert(!isContractAddr, "NonFungibleToken: unsafe transfer");
445
466
 
@@ -476,8 +497,8 @@ module NonFungibleToken {
476
497
  to: Either<Bytes<32>, ContractAddress>,
477
498
  tokenId: Uint<128>
478
499
  ): [] {
479
- Initializable_assertInitialized();
480
- assert(!_isTargetZero(to), "NonFungibleToken: invalid receiver");
500
+ assertInitialized();
501
+ assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
481
502
  // Setting an "auth" argument enables the `_isAuthorized` check which verifies that the token exists
482
503
  // (fromAddress != 0). Therefore, it is not needed to verify that the return value is not 0 here.
483
504
  const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
@@ -500,9 +521,9 @@ module NonFungibleToken {
500
521
  * @return {Either<Bytes<32>, ContractAddress>} - The owner of the token
501
522
  */
502
523
  export circuit _ownerOf(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
503
- Initializable_assertInitialized();
524
+ assertInitialized();
504
525
  if (!_owners.member(disclose(tokenId))) {
505
- return ZERO();
526
+ return Utils_zeroAccount();
506
527
  }
507
528
 
508
529
  return _owners.lookup(disclose(tokenId));
@@ -521,9 +542,9 @@ module NonFungibleToken {
521
542
  * @return {Either<Bytes<32>, ContractAddress>} - An account approved to spend `tokenId`
522
543
  */
523
544
  export circuit _getApproved(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
524
- Initializable_assertInitialized();
545
+ assertInitialized();
525
546
  if (!_tokenApprovals.member(disclose(tokenId))) {
526
- return ZERO();
547
+ return Utils_zeroAccount();
527
548
  }
528
549
  return _tokenApprovals.lookup(disclose(tokenId));
529
550
  }
@@ -551,11 +572,11 @@ module NonFungibleToken {
551
572
  spender: Either<Bytes<32>, ContractAddress>,
552
573
  tokenId: Uint<128>
553
574
  ): Boolean {
554
- Initializable_assertInitialized();
575
+ assertInitialized();
555
576
  const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
556
577
  const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
557
578
 
558
- return (!_isTargetZero(disclose(canonSpender)) &&
579
+ return (!Utils_isTargetZero(disclose(canonSpender)) &&
559
580
  (disclose(canonOwner) == disclose(canonSpender) ||
560
581
  isApprovedForAll(canonOwner, canonSpender) ||
561
582
  _getApproved(tokenId) == disclose(canonSpender)));
@@ -584,9 +605,9 @@ module NonFungibleToken {
584
605
  spender: Either<Bytes<32>, ContractAddress>,
585
606
  tokenId: Uint<128>
586
607
  ): [] {
587
- Initializable_assertInitialized();
608
+ assertInitialized();
588
609
  if (!_isAuthorized(owner, spender, tokenId)) {
589
- assert(!_isTargetZero(owner), "NonFungibleToken: nonexistent token");
610
+ assert(!Utils_isTargetZero(owner), "NonFungibleToken: nonexistent token");
590
611
  assert(false, "NonFungibleToken: insufficient approval");
591
612
  }
592
613
  }
@@ -610,26 +631,26 @@ module NonFungibleToken {
610
631
  tokenId: Uint<128>,
611
632
  auth: Either<Bytes<32>, ContractAddress>
612
633
  ): Either<Bytes<32>, ContractAddress> {
613
- Initializable_assertInitialized();
634
+ assertInitialized();
614
635
  const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
615
636
  const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
616
637
  const fromAddress = _ownerOf(tokenId);
617
638
 
618
639
  // Perform (optional) operator check
619
- if (!_isTargetZero(disclose(canonAuth))) {
640
+ if (!Utils_isTargetZero(disclose(canonAuth))) {
620
641
  _checkAuthorized(fromAddress, canonAuth, tokenId);
621
642
  }
622
643
 
623
644
  // Execute the update
624
- if (!_isTargetZero(disclose(fromAddress))) {
645
+ if (!Utils_isTargetZero(disclose(fromAddress))) {
625
646
  // Clear approval. No need to re-authorize
626
- _approve(ZERO(), tokenId, ZERO());
647
+ _approve(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
627
648
  const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
628
649
  const newBalance = _balances.lookup(disclose(canonFrom)) - 1 as Uint<128>;
629
650
  _balances.insert(disclose(canonFrom), disclose(newBalance));
630
651
  }
631
652
 
632
- if (!_isTargetZero(disclose(canonTo))) {
653
+ if (!Utils_isTargetZero(disclose(canonTo))) {
633
654
  if (!_balances.member(disclose(canonTo))) {
634
655
  _balances.insert(disclose(canonTo), 0);
635
656
  }
@@ -659,7 +680,7 @@ module NonFungibleToken {
659
680
  * @return {[]} - Empty tuple.
660
681
  */
661
682
  export circuit _mint(to: Either<Bytes<32>, ContractAddress>, tokenId: Uint<128>): [] {
662
- Initializable_assertInitialized();
683
+ assertInitialized();
663
684
  const isContractAddr = !to.is_left;
664
685
  assert(!isContractAddr, "NonFungibleToken: unsafe transfer");
665
686
 
@@ -689,12 +710,12 @@ module NonFungibleToken {
689
710
  to: Either<Bytes<32>, ContractAddress>,
690
711
  tokenId: Uint<128>
691
712
  ): [] {
692
- Initializable_assertInitialized();
693
- assert(!_isTargetZero(to), "NonFungibleToken: invalid receiver");
713
+ assertInitialized();
714
+ assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
694
715
 
695
- const previousOwner = _update(to, tokenId, ZERO());
716
+ const previousOwner = _update(to, tokenId, Utils_zeroAccount());
696
717
 
697
- assert(_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
718
+ assert(Utils_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
698
719
  }
699
720
 
700
721
  /**
@@ -713,9 +734,9 @@ module NonFungibleToken {
713
734
  * @return {[]} - Empty tuple.
714
735
  */
715
736
  export circuit _burn(tokenId: Uint<128>): [] {
716
- Initializable_assertInitialized();
717
- const previousOwner = _update(ZERO(), tokenId, ZERO());
718
- assert(!_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
737
+ assertInitialized();
738
+ const previousOwner = _update(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
739
+ assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
719
740
  _tokenURIs.remove(disclose(tokenId));
720
741
  }
721
742
 
@@ -746,7 +767,7 @@ module NonFungibleToken {
746
767
  to: Either<Bytes<32>, ContractAddress>,
747
768
  tokenId: Uint<128>
748
769
  ): [] {
749
- Initializable_assertInitialized();
770
+ assertInitialized();
750
771
  const isContractAddr = !to.is_left;
751
772
  assert(!isContractAddr, "NonFungibleToken: unsafe transfer");
752
773
 
@@ -780,12 +801,12 @@ module NonFungibleToken {
780
801
  to: Either<Bytes<32>, ContractAddress>,
781
802
  tokenId: Uint<128>
782
803
  ): [] {
783
- Initializable_assertInitialized();
784
- assert(!_isTargetZero(to), "NonFungibleToken: invalid receiver");
804
+ assertInitialized();
805
+ assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
785
806
 
786
- const previousOwner = _update(to, tokenId, ZERO());
807
+ const previousOwner = _update(to, tokenId, Utils_zeroAccount());
787
808
 
788
- assert(!_isTargetZero(previousOwner), "NonFungibleToken: nonexistent token");
809
+ assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: nonexistent token");
789
810
 
790
811
  const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
791
812
  assert(previousOwner == canonFrom, "NonFungibleToken: incorrect owner");
@@ -812,15 +833,15 @@ module NonFungibleToken {
812
833
  tokenId: Uint<128>,
813
834
  auth: Either<Bytes<32>, ContractAddress>
814
835
  ): [] {
815
- Initializable_assertInitialized();
836
+ assertInitialized();
816
837
  // Canonicalize + normalize `to`
817
838
  const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
818
- const normalizedTo = _isTargetZero(canonTo) ? ZERO() : canonTo;
839
+ const normalizedTo = Utils_isTargetZero(canonTo) ? Utils_zeroAccount() : canonTo;
819
840
 
820
841
  // Canonicalize auth
821
842
  const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
822
843
 
823
- if (!_isTargetZero(disclose(canonAuth))) {
844
+ if (!Utils_isTargetZero(disclose(canonAuth))) {
824
845
  const owner = _requireOwned(tokenId);
825
846
 
826
847
  // We do not use _isAuthorized because single-token approvals should not be able to call approve
@@ -853,12 +874,12 @@ module NonFungibleToken {
853
874
  operator: Either<Bytes<32>, ContractAddress>,
854
875
  approved: Boolean
855
876
  ): [] {
856
- Initializable_assertInitialized();
877
+ assertInitialized();
857
878
  const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
858
- assert(!_isTargetZero(canonOwner), "NonFungibleToken: invalid owner");
879
+ assert(!Utils_isTargetZero(canonOwner), "NonFungibleToken: invalid owner");
859
880
 
860
881
  const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
861
- assert(!_isTargetZero(canonOp), "NonFungibleToken: invalid operator");
882
+ assert(!Utils_isTargetZero(canonOp), "NonFungibleToken: invalid operator");
862
883
 
863
884
  if (!_operatorApprovals.member(disclose(canonOwner))) {
864
885
  _operatorApprovals.insert(
@@ -885,10 +906,10 @@ module NonFungibleToken {
885
906
  * @return {Either<Bytes<32>, ContractAddress>} - The owner of `tokenId`
886
907
  */
887
908
  export circuit _requireOwned(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
888
- Initializable_assertInitialized();
909
+ assertInitialized();
889
910
  const owner = _ownerOf(tokenId);
890
911
 
891
- assert(!_isTargetZero(owner), "NonFungibleToken: nonexistent token");
912
+ assert(!Utils_isTargetZero(owner), "NonFungibleToken: nonexistent token");
892
913
  return owner;
893
914
  }
894
915
 
@@ -903,52 +924,6 @@ module NonFungibleToken {
903
924
  * @returns {Bytes<32>} accountId - The computed account identifier.
904
925
  */
905
926
  circuit _computeAccountId(): Bytes<32> {
906
- return computeAccountId(wit_NonFungibleTokenSK());
907
- }
908
-
909
- /**
910
- * @description Computes an account identifier without on-chain state, allowing a user to derive
911
- * their identity commitment before submitting it in a token operation.
912
- * This is the off-chain counterpart to {_computeAccountId} and produces an identical result
913
- * given the same inputs.
914
- *
915
- * @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
916
- * permanently compromise the security of this system:
917
- *
918
- * - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
919
- * application logs, analytics pipelines, or any observable side-channel.
920
- * - **Store offline or in secure enclaves** — hardware security modules (HSMs),
921
- * air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
922
- * - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
923
- * or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
924
- * - **Treat key loss as identity loss** — a lost key cannot be recovered.
925
- * - **Avoid calling this circuit in untrusted environments** — executing this in an
926
- * unverified browser extension, compromised runtime, or shared machine may expose
927
- * the key to a malicious observer.
928
- *
929
- * ## ID Derivation
930
- * `accountId = persistentHash(secretKey)`
931
- *
932
- * @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
933
- *
934
- * @returns {Bytes<32>} accountId - The computed account identifier.
935
- */
936
- export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
937
- return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
938
- }
939
-
940
- /**
941
- * @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
942
- * holds the zero value.
943
- *
944
- * @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
945
- * @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
946
- */
947
- circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
948
- if (target.is_left) {
949
- return target.left == default<Bytes<32>>;
950
- } else {
951
- return target.right == default<ContractAddress>;
952
- }
927
+ return Utils_computeAccountId(wit_NonFungibleTokenSK());
953
928
  }
954
929
  }
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.1.0 (utils/Utils.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-alpha (utils/Utils.compact)
3
3
 
4
- pragma language_version >= 0.21.0;
4
+ pragma language_version >= 0.23.0;
5
5
 
6
6
  /**
7
7
  * @module Utils.
@@ -95,4 +95,88 @@ module Utils {
95
95
  ? Either<T1, T2>{ is_left: true, left: value.left, right: default<T2> }
96
96
  : Either<T1, T2>{ is_left: false, left: default<T1>, right: value.right };
97
97
  }
98
+
99
+ /**
100
+ * @description Returns the canonical zero `Either<Bytes<32>, ContractAddress>` value: the
101
+ * left variant (`Bytes<32>`) set to zero. Used as the sentinel for mint/burn operations
102
+ * (a zero `from` signals a mint, a zero `to` signals a burn) and for cleared approvals.
103
+ *
104
+ * The left variant is chosen so that `isTargetZero` checks against `default<Bytes<32>>`,
105
+ * and to avoid triggering contract-address guards (e.g. circuits that check `!to.is_left`
106
+ * before delegating to an `_unsafe` variant). A right variant with `default<ContractAddress>`
107
+ * would also pass `isTargetZero`, but the left variant keeps guards and error messages consistent.
108
+ *
109
+ * @return {Either<Bytes<32>, ContractAddress>} - The canonical zero value.
110
+ */
111
+ export pure circuit zeroAccount(): Either<Bytes<32>, ContractAddress> {
112
+ return Either<Bytes<32>, ContractAddress> {
113
+ is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
114
+ };
115
+ }
116
+
117
+ /**
118
+ * @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
119
+ * holds the zero value.
120
+ *
121
+ * @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
122
+ * @return {Boolean} - `true` if the active branch is zero, `false` otherwise.
123
+ */
124
+ export pure circuit isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
125
+ if (target.is_left) {
126
+ return target.left == default<Bytes<32>>;
127
+ } else {
128
+ return target.right == default<ContractAddress>;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * @description Computes an account identifier without on-chain state, allowing a user to derive
134
+ * their identity commitment before submitting an operation.
135
+ *
136
+ * @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
137
+ * permanently compromise the security of this system:
138
+ *
139
+ * - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
140
+ * application logs, analytics pipelines, or any observable side-channel.
141
+ * - **Store offline or in secure enclaves** — hardware security modules (HSMs),
142
+ * air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
143
+ * - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
144
+ * or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
145
+ * - **Treat key loss as identity loss** — a lost key cannot be recovered.
146
+ * - **Avoid calling this circuit in untrusted environments** — executing this in an
147
+ * unverified browser extension, compromised runtime, or shared machine may expose
148
+ * the key to a malicious observer.
149
+ *
150
+ * ## ID Derivation
151
+ * `accountId = persistentHash(secretKey)`
152
+ *
153
+ * @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
154
+ *
155
+ * @returns {Bytes<32>} accountId - The computed account identifier.
156
+ */
157
+ export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
158
+ return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
159
+ }
160
+
161
+ /**
162
+ * @description Returns the current contract's address as an
163
+ * `Either<ZswapCoinPublicKey, ContractAddress>` for use as a
164
+ * recipient in shielded send operations (deposits and receiving change).
165
+ *
166
+ * @returns {Either<ZswapCoinPublicKey, ContractAddress>} The contract's address as a recipient.
167
+ */
168
+ export circuit selfAsRecipient(): Either<ZswapCoinPublicKey, ContractAddress> {
169
+ return right<ZswapCoinPublicKey, ContractAddress>(kernel.self());
170
+ }
171
+
172
+ /**
173
+ * @description The maximum value representable by a `Uint<128>`.
174
+ *
175
+ * @notice TODO: Remove once a math module providing this constant is available.
176
+ *
177
+ * @returns {Uint<128>} `2^128 - 1`.
178
+ */
179
+ export pure circuit UINT128_MAX(): Uint<128> {
180
+ return 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
181
+ }
98
182
  }