@openzeppelin/compact-contracts 0.2.0 → 0.3.0-alpha.1
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.
- package/README.md +10 -10
- package/access/AccessControl.compact +3 -35
- package/access/Ownable.compact +5 -51
- package/access/ShieldedAccessControl.compact +2 -2
- package/access/ZOwnablePK.compact +2 -2
- package/crypto/EcdhMask.compact +177 -0
- package/crypto/ElGamal.compact +473 -0
- package/multisig/ForwarderPrivate.compact +233 -0
- package/multisig/ForwarderShielded.compact +149 -0
- package/multisig/ForwarderUnshielded.compact +152 -0
- package/multisig/ProposalManager.compact +328 -0
- package/multisig/ShieldedTreasury.compact +204 -0
- package/multisig/ShieldedTreasuryStateless.compact +80 -0
- package/multisig/Signer.compact +312 -0
- package/multisig/SignerManager.compact +205 -0
- package/multisig/UnshieldedTreasury.compact +114 -0
- package/multisig/presets/ShieldedMultiSig.compact +227 -0
- package/multisig/presets/ShieldedMultiSigV2.compact +280 -0
- package/multisig/presets/ShieldedMultiSigV3.compact +368 -0
- package/multisig/presets/forwarder/ForwarderPrivate.compact +125 -0
- package/multisig/presets/forwarder/ForwarderShielded.compact +62 -0
- package/multisig/presets/forwarder/ForwarderUnshielded.compact +64 -0
- package/package.json +32 -16
- package/security/Allowlist.compact +101 -0
- package/security/Blocklist.compact +99 -0
- package/security/Initializable.compact +2 -2
- package/security/Pausable.compact +2 -2
- package/token/ConfidentialFungibleToken.compact +1180 -0
- package/token/FungibleToken.compact +24 -82
- package/token/MultiToken.compact +21 -80
- package/token/NativeShieldedToken.compact +285 -0
- package/token/NativeShieldedTokenCore.compact +343 -0
- package/token/NativeShieldedTokenFamily.compact +287 -0
- package/token/NonFungibleToken.compact +25 -83
- package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +90 -0
- package/token/extensions/NativeShieldedTokenDerivedNonce.compact +70 -0
- package/token/extensions/NativeShieldedTokenFamilySupply.compact +145 -0
- package/token/extensions/NativeShieldedTokenSupply.compact +142 -0
- package/token/extensions/NativeShieldedTokenSupplyCore.compact +163 -0
- package/utils/Utils.compact +86 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/NonFungibleToken.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module NonFungibleToken
|
|
@@ -154,18 +154,6 @@ module NonFungibleToken {
|
|
|
154
154
|
*/
|
|
155
155
|
witness wit_NonFungibleTokenSK(): Bytes<32>;
|
|
156
156
|
|
|
157
|
-
/**
|
|
158
|
-
* @description Returns a canonical zero Either value (left variant with zero Bytes<32>).
|
|
159
|
-
* Used as the zero value for mint/burn operations and cleared approvals.
|
|
160
|
-
*
|
|
161
|
-
* @return {Either<Bytes<32>, ContractAddress>} - The zero value.
|
|
162
|
-
*/
|
|
163
|
-
export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
|
|
164
|
-
return Either<Bytes<32>, ContractAddress> {
|
|
165
|
-
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
157
|
/**
|
|
170
158
|
* @description Initializes the contract by setting the name and symbol.
|
|
171
159
|
*
|
|
@@ -510,7 +498,7 @@ module NonFungibleToken {
|
|
|
510
498
|
tokenId: Uint<128>
|
|
511
499
|
): [] {
|
|
512
500
|
assertInitialized();
|
|
513
|
-
assert(!
|
|
501
|
+
assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
514
502
|
// Setting an "auth" argument enables the `_isAuthorized` check which verifies that the token exists
|
|
515
503
|
// (fromAddress != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
|
516
504
|
const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
@@ -535,7 +523,7 @@ module NonFungibleToken {
|
|
|
535
523
|
export circuit _ownerOf(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
536
524
|
assertInitialized();
|
|
537
525
|
if (!_owners.member(disclose(tokenId))) {
|
|
538
|
-
return
|
|
526
|
+
return Utils_zeroAccount();
|
|
539
527
|
}
|
|
540
528
|
|
|
541
529
|
return _owners.lookup(disclose(tokenId));
|
|
@@ -556,7 +544,7 @@ module NonFungibleToken {
|
|
|
556
544
|
export circuit _getApproved(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
557
545
|
assertInitialized();
|
|
558
546
|
if (!_tokenApprovals.member(disclose(tokenId))) {
|
|
559
|
-
return
|
|
547
|
+
return Utils_zeroAccount();
|
|
560
548
|
}
|
|
561
549
|
return _tokenApprovals.lookup(disclose(tokenId));
|
|
562
550
|
}
|
|
@@ -588,7 +576,7 @@ module NonFungibleToken {
|
|
|
588
576
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
589
577
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
590
578
|
|
|
591
|
-
return (!
|
|
579
|
+
return (!Utils_isTargetZero(disclose(canonSpender)) &&
|
|
592
580
|
(disclose(canonOwner) == disclose(canonSpender) ||
|
|
593
581
|
isApprovedForAll(canonOwner, canonSpender) ||
|
|
594
582
|
_getApproved(tokenId) == disclose(canonSpender)));
|
|
@@ -619,7 +607,7 @@ module NonFungibleToken {
|
|
|
619
607
|
): [] {
|
|
620
608
|
assertInitialized();
|
|
621
609
|
if (!_isAuthorized(owner, spender, tokenId)) {
|
|
622
|
-
assert(!
|
|
610
|
+
assert(!Utils_isTargetZero(owner), "NonFungibleToken: nonexistent token");
|
|
623
611
|
assert(false, "NonFungibleToken: insufficient approval");
|
|
624
612
|
}
|
|
625
613
|
}
|
|
@@ -649,20 +637,20 @@ module NonFungibleToken {
|
|
|
649
637
|
const fromAddress = _ownerOf(tokenId);
|
|
650
638
|
|
|
651
639
|
// Perform (optional) operator check
|
|
652
|
-
if (!
|
|
640
|
+
if (!Utils_isTargetZero(disclose(canonAuth))) {
|
|
653
641
|
_checkAuthorized(fromAddress, canonAuth, tokenId);
|
|
654
642
|
}
|
|
655
643
|
|
|
656
644
|
// Execute the update
|
|
657
|
-
if (!
|
|
645
|
+
if (!Utils_isTargetZero(disclose(fromAddress))) {
|
|
658
646
|
// Clear approval. No need to re-authorize
|
|
659
|
-
_approve(
|
|
647
|
+
_approve(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
|
|
660
648
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
661
649
|
const newBalance = _balances.lookup(disclose(canonFrom)) - 1 as Uint<128>;
|
|
662
650
|
_balances.insert(disclose(canonFrom), disclose(newBalance));
|
|
663
651
|
}
|
|
664
652
|
|
|
665
|
-
if (!
|
|
653
|
+
if (!Utils_isTargetZero(disclose(canonTo))) {
|
|
666
654
|
if (!_balances.member(disclose(canonTo))) {
|
|
667
655
|
_balances.insert(disclose(canonTo), 0);
|
|
668
656
|
}
|
|
@@ -723,11 +711,11 @@ module NonFungibleToken {
|
|
|
723
711
|
tokenId: Uint<128>
|
|
724
712
|
): [] {
|
|
725
713
|
assertInitialized();
|
|
726
|
-
assert(!
|
|
714
|
+
assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
727
715
|
|
|
728
|
-
const previousOwner = _update(to, tokenId,
|
|
716
|
+
const previousOwner = _update(to, tokenId, Utils_zeroAccount());
|
|
729
717
|
|
|
730
|
-
assert(
|
|
718
|
+
assert(Utils_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
|
|
731
719
|
}
|
|
732
720
|
|
|
733
721
|
/**
|
|
@@ -747,8 +735,8 @@ module NonFungibleToken {
|
|
|
747
735
|
*/
|
|
748
736
|
export circuit _burn(tokenId: Uint<128>): [] {
|
|
749
737
|
assertInitialized();
|
|
750
|
-
const previousOwner = _update(
|
|
751
|
-
assert(!
|
|
738
|
+
const previousOwner = _update(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
|
|
739
|
+
assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
|
|
752
740
|
_tokenURIs.remove(disclose(tokenId));
|
|
753
741
|
}
|
|
754
742
|
|
|
@@ -814,11 +802,11 @@ module NonFungibleToken {
|
|
|
814
802
|
tokenId: Uint<128>
|
|
815
803
|
): [] {
|
|
816
804
|
assertInitialized();
|
|
817
|
-
assert(!
|
|
805
|
+
assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
818
806
|
|
|
819
|
-
const previousOwner = _update(to, tokenId,
|
|
807
|
+
const previousOwner = _update(to, tokenId, Utils_zeroAccount());
|
|
820
808
|
|
|
821
|
-
assert(!
|
|
809
|
+
assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: nonexistent token");
|
|
822
810
|
|
|
823
811
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
824
812
|
assert(previousOwner == canonFrom, "NonFungibleToken: incorrect owner");
|
|
@@ -848,12 +836,12 @@ module NonFungibleToken {
|
|
|
848
836
|
assertInitialized();
|
|
849
837
|
// Canonicalize + normalize `to`
|
|
850
838
|
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
851
|
-
const normalizedTo =
|
|
839
|
+
const normalizedTo = Utils_isTargetZero(canonTo) ? Utils_zeroAccount() : canonTo;
|
|
852
840
|
|
|
853
841
|
// Canonicalize auth
|
|
854
842
|
const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
|
|
855
843
|
|
|
856
|
-
if (!
|
|
844
|
+
if (!Utils_isTargetZero(disclose(canonAuth))) {
|
|
857
845
|
const owner = _requireOwned(tokenId);
|
|
858
846
|
|
|
859
847
|
// We do not use _isAuthorized because single-token approvals should not be able to call approve
|
|
@@ -888,10 +876,10 @@ module NonFungibleToken {
|
|
|
888
876
|
): [] {
|
|
889
877
|
assertInitialized();
|
|
890
878
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
891
|
-
assert(!
|
|
879
|
+
assert(!Utils_isTargetZero(canonOwner), "NonFungibleToken: invalid owner");
|
|
892
880
|
|
|
893
881
|
const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
|
|
894
|
-
assert(!
|
|
882
|
+
assert(!Utils_isTargetZero(canonOp), "NonFungibleToken: invalid operator");
|
|
895
883
|
|
|
896
884
|
if (!_operatorApprovals.member(disclose(canonOwner))) {
|
|
897
885
|
_operatorApprovals.insert(
|
|
@@ -921,7 +909,7 @@ module NonFungibleToken {
|
|
|
921
909
|
assertInitialized();
|
|
922
910
|
const owner = _ownerOf(tokenId);
|
|
923
911
|
|
|
924
|
-
assert(!
|
|
912
|
+
assert(!Utils_isTargetZero(owner), "NonFungibleToken: nonexistent token");
|
|
925
913
|
return owner;
|
|
926
914
|
}
|
|
927
915
|
|
|
@@ -936,52 +924,6 @@ module NonFungibleToken {
|
|
|
936
924
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
937
925
|
*/
|
|
938
926
|
circuit _computeAccountId(): Bytes<32> {
|
|
939
|
-
return
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
944
|
-
* their identity commitment before submitting it in a token operation.
|
|
945
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
946
|
-
* given the same inputs.
|
|
947
|
-
*
|
|
948
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
949
|
-
* permanently compromise the security of this system:
|
|
950
|
-
*
|
|
951
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
952
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
953
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
954
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
955
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
956
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
957
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
958
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
959
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
960
|
-
* the key to a malicious observer.
|
|
961
|
-
*
|
|
962
|
-
* ## ID Derivation
|
|
963
|
-
* `accountId = persistentHash(secretKey)`
|
|
964
|
-
*
|
|
965
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
966
|
-
*
|
|
967
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
968
|
-
*/
|
|
969
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
970
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
/**
|
|
974
|
-
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
975
|
-
* holds the zero value.
|
|
976
|
-
*
|
|
977
|
-
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
978
|
-
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
979
|
-
*/
|
|
980
|
-
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
981
|
-
if (target.is_left) {
|
|
982
|
-
return target.left == default<Bytes<32>>;
|
|
983
|
-
} else {
|
|
984
|
-
return target.right == default<ContractAddress>;
|
|
985
|
-
}
|
|
927
|
+
return Utils_computeAccountId(wit_NonFungibleTokenSK());
|
|
986
928
|
}
|
|
987
929
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/extensions/ConfidentialFungibleTokenPublicSupply.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module ConfidentialFungibleTokenPublicSupply
|
|
8
|
+
* @description Optional standalone extension that adds public supply
|
|
9
|
+
* accounting to a `ConfidentialFungibleToken`. It exposes the scalar
|
|
10
|
+
* `_addSupply` / `_subSupply` building blocks plus the `totalSupply` getter.
|
|
11
|
+
* It imports no token module.
|
|
12
|
+
*
|
|
13
|
+
* "Public" supply means the total, and therefore each mint/burn delta, is
|
|
14
|
+
* disclosed on chain. Balances stay confidential; only the aggregate is
|
|
15
|
+
* visible. This is the right default for issuers who must attest to a public
|
|
16
|
+
* circulating supply (stablecoins, tokenized funds). A confidential-supply
|
|
17
|
+
* extension (ElGamal supply cell + auditor viewing key) is a separate variant.
|
|
18
|
+
*
|
|
19
|
+
* @notice Pairs with `ConfidentialFungibleToken`. The consuming contract
|
|
20
|
+
* composes the pieces, calling the accounting block alongside the matching
|
|
21
|
+
* token op (the integration suite drives an assembled example,
|
|
22
|
+
* `test/integration/_mocks/ConfidentialFungibleTokenPublicSupply`):
|
|
23
|
+
*
|
|
24
|
+
* export circuit mint(account: Bytes<32>, value: Uint<128>): [] {
|
|
25
|
+
* Supply__addSupply(value);
|
|
26
|
+
* Token__mint(account, value);
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* The pairing is the consuming contract's responsibility: a token op called
|
|
30
|
+
* without its accounting block (or vice versa) breaks the
|
|
31
|
+
* `sum(balances) == totalSupply` invariant this extension exists to attest.
|
|
32
|
+
*/
|
|
33
|
+
module ConfidentialFungibleTokenPublicSupply {
|
|
34
|
+
import CompactStandardLibrary;
|
|
35
|
+
|
|
36
|
+
// The public circulating supply. Mint and burn disclose their amounts
|
|
37
|
+
// through the delta on this value.
|
|
38
|
+
export ledger _totalSupply: Uint<128>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @description Increases `totalSupply` by `value`. Call alongside the token
|
|
42
|
+
* module's `_mint`. The added amount is disclosed via the supply delta.
|
|
43
|
+
*
|
|
44
|
+
* @circuitInfo k=9, rows=401
|
|
45
|
+
*
|
|
46
|
+
* Requirements:
|
|
47
|
+
*
|
|
48
|
+
* - `totalSupply + value` does not overflow `Uint<128>`.
|
|
49
|
+
*
|
|
50
|
+
* @param {Uint<128>} value - The amount added to the supply.
|
|
51
|
+
* @return {[]} - Empty tuple.
|
|
52
|
+
*/
|
|
53
|
+
export circuit _addSupply(value: Uint<128>): [] {
|
|
54
|
+
const MAX_UINT128 = 340282366920938463463374607431768211455;
|
|
55
|
+
assert(MAX_UINT128 - _totalSupply >= value,
|
|
56
|
+
"ConfidentialFungibleTokenPublicSupply: overflow");
|
|
57
|
+
_totalSupply = disclose(_totalSupply + value as Uint<128>);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @description Decreases `totalSupply` by `value`. Call alongside the token
|
|
62
|
+
* module's `_burn` or `_burnFrom`. The removed amount is disclosed via the
|
|
63
|
+
* supply delta.
|
|
64
|
+
*
|
|
65
|
+
* @circuitInfo k=9, rows=256
|
|
66
|
+
*
|
|
67
|
+
* Requirements:
|
|
68
|
+
*
|
|
69
|
+
* - `totalSupply` is at least `value`.
|
|
70
|
+
*
|
|
71
|
+
* @param {Uint<128>} value - The amount removed from the supply.
|
|
72
|
+
* @return {[]} - Empty tuple.
|
|
73
|
+
*/
|
|
74
|
+
export circuit _subSupply(value: Uint<128>): [] {
|
|
75
|
+
assert(_totalSupply >= value,
|
|
76
|
+
"ConfidentialFungibleTokenPublicSupply: supply underflow");
|
|
77
|
+
_totalSupply = disclose(_totalSupply - value as Uint<128>);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @description Returns the public total token supply.
|
|
82
|
+
*
|
|
83
|
+
* @circuitInfo k=6, rows=26
|
|
84
|
+
*
|
|
85
|
+
* @return {Uint<128>} - The total supply.
|
|
86
|
+
*/
|
|
87
|
+
export circuit totalSupply(): Uint<128> {
|
|
88
|
+
return _totalSupply;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/extensions/NativeShieldedTokenDerivedNonce.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenDerivedNonce
|
|
8
|
+
* @description Optional standalone extension providing derived coin nonces for
|
|
9
|
+
* native shielded token mints, so callers need not manage nonces themselves. It
|
|
10
|
+
* owns a monotonic `_counter` and exposes `_deriveNonce`; it imports no token
|
|
11
|
+
* module and needs no initialization.
|
|
12
|
+
*
|
|
13
|
+
* @notice Pairs with `NativeShieldedToken` and `NativeShieldedTokenFamily`. The
|
|
14
|
+
* consuming contract composes the pieces:
|
|
15
|
+
*
|
|
16
|
+
* export circuit mint(recipient: ..., amount: Uint<64>): ShieldedCoinInfo {
|
|
17
|
+
* return Token__mint(recipient, amount, Derived__deriveNonce());
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* @dev Derivation
|
|
21
|
+
* Each call derives `evolveNonce(_counter, pad(32, "NativeShieldedToken:nonce"))`.
|
|
22
|
+
* `evolveNonce` hashes the counter into a full 32-byte nonce, so uniqueness comes
|
|
23
|
+
* from the monotonic counter, not the tag. The fixed tag is a domain separator:
|
|
24
|
+
* it keeps these derived nonces from clashing with any other `evolveNonce` call
|
|
25
|
+
* the implementing contract makes on the same counter for a different purpose.
|
|
26
|
+
* One counter serves the whole contract, so derived nonces never repeat across
|
|
27
|
+
* token types (domains) of one contract.
|
|
28
|
+
*
|
|
29
|
+
* @notice This variant is deterministic and recipient-PUBLIC by design: derived
|
|
30
|
+
* nonces are predictable from public state, so anyone can recompute the coin
|
|
31
|
+
* commitment for candidate recipient keys. For recipient privacy, call the base
|
|
32
|
+
* `_mint` with a secret, cryptographically random nonce instead. (An earlier
|
|
33
|
+
* design seeded a hash chain to make the sequence look random; that bought
|
|
34
|
+
* nothing here because the seed and chain are disclosed to public state, so it
|
|
35
|
+
* was removed.)
|
|
36
|
+
*
|
|
37
|
+
* @notice Collision-griefing. Derived nonces are predictable, so an actor with a
|
|
38
|
+
* caller-nonce mint can pre-mint a colliding commitment and make the matching
|
|
39
|
+
* derived-nonce mint fail. Mitigations:
|
|
40
|
+
* - Access-control both mint paths, and avoid exposing them at different trust
|
|
41
|
+
* levels.
|
|
42
|
+
* - Do not rely on retrying. The nonce is a pure function of the counter, and a
|
|
43
|
+
* reverted mint will not advance it, so an identical retry collides again.
|
|
44
|
+
* Recovery is a mint with different inputs (recipient or amount): it advances
|
|
45
|
+
* the counter, so the original tuple then mints against a fresh nonce.
|
|
46
|
+
*/
|
|
47
|
+
module NativeShieldedTokenDerivedNonce {
|
|
48
|
+
import CompactStandardLibrary;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @description Monotonic index feeding the derived-nonce derivation.
|
|
52
|
+
*/
|
|
53
|
+
export ledger _counter: Counter;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @description Advances the counter and returns the next derived coin nonce,
|
|
57
|
+
* for use as the `nonce` argument of a native token mint.
|
|
58
|
+
*
|
|
59
|
+
* @notice The derivation input is public ledger state, so mints using this
|
|
60
|
+
* nonce are recipient-public; see the module notes.
|
|
61
|
+
*
|
|
62
|
+
* @circuitInfo k=9, rows=451
|
|
63
|
+
*
|
|
64
|
+
* @return {Bytes<32>} - The next derived coin nonce.
|
|
65
|
+
*/
|
|
66
|
+
export circuit _deriveNonce(): Bytes<32> {
|
|
67
|
+
_counter.increment(1);
|
|
68
|
+
return evolveNonce(_counter, pad(32, "NativeShieldedToken:nonce"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.2.0 (token/extensions/NativeShieldedTokenFamilySupply.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenFamilySupply
|
|
8
|
+
* @description Optional standalone extension that adds per-domain on-chain
|
|
9
|
+
* supply accounting to a `NativeShieldedTokenFamily`. It exposes `_addMinted` /
|
|
10
|
+
* `_addBurned` building blocks plus the `totalMinted` / `totalBurned` /
|
|
11
|
+
* `totalSupply` getters, each keyed by domain. It imports no token module.
|
|
12
|
+
*
|
|
13
|
+
* It is a thin wrapper over `NativeShieldedTokenSupplyCore`, forwarding the
|
|
14
|
+
* caller's per-call `domain`, so the accounting logic and guarantees live in
|
|
15
|
+
* the shared core.
|
|
16
|
+
*
|
|
17
|
+
* @notice Pairs with `NativeShieldedTokenFamily`. The consuming contract
|
|
18
|
+
* composes the pieces, calling the accounting block alongside the matching
|
|
19
|
+
* token op:
|
|
20
|
+
*
|
|
21
|
+
* export circuit mint(
|
|
22
|
+
* domain: Bytes<32>,
|
|
23
|
+
* recipient: Either<ZswapCoinPublicKey, ContractAddress>,
|
|
24
|
+
* amount: Uint<64>,
|
|
25
|
+
* nonce: Bytes<32>
|
|
26
|
+
* ): ShieldedCoinInfo {
|
|
27
|
+
* const coin = Family__mint(domain, recipient, amount, nonce);
|
|
28
|
+
* Supply__addMinted(domain, amount);
|
|
29
|
+
* return coin;
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* export circuit burn(
|
|
33
|
+
* domain: Bytes<32>,
|
|
34
|
+
* coin: ShieldedCoinInfo,
|
|
35
|
+
* amount: Uint<128>,
|
|
36
|
+
* refundTo: Either<ZswapCoinPublicKey, ContractAddress>
|
|
37
|
+
* ): Maybe<ShieldedCoinInfo> {
|
|
38
|
+
* const refund = Family__burn(domain, coin, amount, refundTo);
|
|
39
|
+
* Supply__addBurned(domain, amount);
|
|
40
|
+
* return refund;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* @notice Privacy trade-off (read before composing): composing this extension
|
|
44
|
+
* makes contract-mediated BURN amounts PUBLIC. The bare family's `_burn` /
|
|
45
|
+
* `_burnFromSelf` are amount-private (their coin operations emit only
|
|
46
|
+
* commitments and nullifiers, never the value; the `disclose()` wrappers they
|
|
47
|
+
* require are compiler permission markers, not disclosure sinks). The
|
|
48
|
+
* `_addBurned` write here is what puts the burned amount into the public
|
|
49
|
+
* transcript. Compose this only when on-chain auditability is worth more than
|
|
50
|
+
* burn-amount privacy.
|
|
51
|
+
*
|
|
52
|
+
* @dev Mint amounts are already public via the protocol's `shieldedMints`
|
|
53
|
+
* effect, so `_addMinted` adds accounting, not disclosure. See
|
|
54
|
+
* `NativeShieldedTokenSupplyCore` for the exact / lower-bound / upper-bound
|
|
55
|
+
* guarantees, the per-domain `burned <= minted` invariant, and the mint-side
|
|
56
|
+
* drift caveat.
|
|
57
|
+
*
|
|
58
|
+
* @warning Pair every mint with `_addMinted` and every burn with `_addBurned` on
|
|
59
|
+
* every path, under the matching domain; mis-wiring is a security-critical,
|
|
60
|
+
* undetectable error (e.g. an undercounted mint makes `_addBurned` reject
|
|
61
|
+
* legitimate burns). See `NativeShieldedTokenSupplyCore`.
|
|
62
|
+
*/
|
|
63
|
+
module NativeShieldedTokenFamilySupply {
|
|
64
|
+
import CompactStandardLibrary;
|
|
65
|
+
import "./NativeShieldedTokenSupplyCore" prefix Core_;
|
|
66
|
+
|
|
67
|
+
// Circuits use the `Core_` prefix above. The named import/re-export below is
|
|
68
|
+
// only for the core supply ledger keys, so the implementing contract's ledger
|
|
69
|
+
// keys read as `_totalMinted` / `_totalBurned` rather than
|
|
70
|
+
// `NativeShieldedTokenSupplyCore__totalMinted`. The circuit names are
|
|
71
|
+
// intentionally NOT imported unprefixed, to avoid potential clashes.
|
|
72
|
+
import { _totalMinted, _totalBurned } from "./NativeShieldedTokenSupplyCore";
|
|
73
|
+
export { _totalMinted, _totalBurned };
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @description Adds `amount` to the exact minted total for `domain`. Call
|
|
77
|
+
* once per successful `NativeShieldedTokenFamily._mint`.
|
|
78
|
+
*
|
|
79
|
+
* @circuitInfo k=10, rows=653
|
|
80
|
+
*
|
|
81
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
82
|
+
* @param {Uint<64>} amount - The minted amount.
|
|
83
|
+
* @return {[]} - Empty tuple.
|
|
84
|
+
*/
|
|
85
|
+
export circuit _addMinted(domain: Bytes<32>, amount: Uint<64>): [] {
|
|
86
|
+
Core__addMinted(domain, amount);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @description Adds `amount` to the contract-mediated burned total for
|
|
91
|
+
* `domain`. Call once per successful `NativeShieldedTokenFamily._burn` /
|
|
92
|
+
* `_burnFromSelf`.
|
|
93
|
+
* @dev Enforces per-domain `burned <= minted`; an unpaired, duplicate, or
|
|
94
|
+
* wrong-domain burn reverts.
|
|
95
|
+
*
|
|
96
|
+
* @circuitInfo k=10, rows=755
|
|
97
|
+
*
|
|
98
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
99
|
+
* @param {Uint<128>} amount - The burned amount.
|
|
100
|
+
* @return {[]} - Empty tuple.
|
|
101
|
+
*/
|
|
102
|
+
export circuit _addBurned(domain: Bytes<32>, amount: Uint<128>): [] {
|
|
103
|
+
Core__addBurned(domain, amount);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @description Returns the exact amount ever minted for `domain`.
|
|
108
|
+
*
|
|
109
|
+
* @circuitInfo k=9, rows=344
|
|
110
|
+
*
|
|
111
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
112
|
+
* @return {Uint<128>} - The total amount minted.
|
|
113
|
+
*/
|
|
114
|
+
export circuit totalMinted(domain: Bytes<32>): Uint<128> {
|
|
115
|
+
return Core_totalMinted(domain);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @description Returns the contract-mediated amount burned for `domain`.
|
|
120
|
+
* @notice This is a lower bound: coins sent directly to the burn address
|
|
121
|
+
* without going through the contract are not counted.
|
|
122
|
+
*
|
|
123
|
+
* @circuitInfo k=9, rows=341
|
|
124
|
+
*
|
|
125
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
126
|
+
* @return {Uint<128>} - The total amount burned through the contract.
|
|
127
|
+
*/
|
|
128
|
+
export circuit totalBurned(domain: Bytes<32>): Uint<128> {
|
|
129
|
+
return Core_totalBurned(domain);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @description Returns `totalMinted(domain) - totalBurned(domain)`.
|
|
134
|
+
* @notice This is an UPPER BOUND on circulating supply, not an exact value:
|
|
135
|
+
* burns that bypass the contract are invisible. See the module notes.
|
|
136
|
+
*
|
|
137
|
+
* @circuitInfo k=9, rows=439
|
|
138
|
+
*
|
|
139
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
140
|
+
* @return {Uint<128>} - The upper bound on tokens in existence.
|
|
141
|
+
*/
|
|
142
|
+
export circuit totalSupply(domain: Bytes<32>): Uint<128> {
|
|
143
|
+
return Core_totalSupply(domain);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.2.0 (token/extensions/NativeShieldedTokenSupply.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenSupply
|
|
8
|
+
* @description Optional standalone extension that adds on-chain supply
|
|
9
|
+
* accounting to a `NativeShieldedToken`. It exposes scalar `_addMinted` /
|
|
10
|
+
* `_addBurned` building blocks plus the `totalMinted` / `totalBurned` /
|
|
11
|
+
* `totalSupply` getters. It imports no token module.
|
|
12
|
+
*
|
|
13
|
+
* It is a thin wrapper over `NativeShieldedTokenSupplyCore`, forwarding a
|
|
14
|
+
* single fixed key (the paired contract has exactly one token type), so the
|
|
15
|
+
* accounting logic and guarantees live in the shared core.
|
|
16
|
+
*
|
|
17
|
+
* @notice Pairs with `NativeShieldedToken`. The consuming contract composes the
|
|
18
|
+
* pieces, calling the accounting block alongside the matching token op:
|
|
19
|
+
*
|
|
20
|
+
* export circuit mint(
|
|
21
|
+
* recipient: Either<ZswapCoinPublicKey, ContractAddress>,
|
|
22
|
+
* amount: Uint<64>,
|
|
23
|
+
* nonce: Bytes<32>
|
|
24
|
+
* ): ShieldedCoinInfo {
|
|
25
|
+
* const coin = Token__mint(recipient, amount, nonce);
|
|
26
|
+
* Supply__addMinted(amount);
|
|
27
|
+
* return coin;
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* export circuit burn(
|
|
31
|
+
* coin: ShieldedCoinInfo,
|
|
32
|
+
* amount: Uint<128>,
|
|
33
|
+
* refundTo: Either<ZswapCoinPublicKey, ContractAddress>
|
|
34
|
+
* ): Maybe<ShieldedCoinInfo> {
|
|
35
|
+
* const refund = Token__burn(coin, amount, refundTo);
|
|
36
|
+
* Supply__addBurned(amount);
|
|
37
|
+
* return refund;
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* @notice Privacy trade-off (read before composing): composing this extension
|
|
41
|
+
* makes contract-mediated BURN amounts PUBLIC. The bare token's `_burn` /
|
|
42
|
+
* `_burnFromSelf` are amount-private (their coin operations emit only
|
|
43
|
+
* commitments and nullifiers, never the value; the `disclose()` wrappers they
|
|
44
|
+
* require are compiler permission markers, not disclosure sinks). The
|
|
45
|
+
* `_addBurned` write here is what puts the burned amount into the public
|
|
46
|
+
* transcript. Compose this only when on-chain auditability is worth more than
|
|
47
|
+
* burn-amount privacy.
|
|
48
|
+
*
|
|
49
|
+
* @dev Mint amounts are already public via the protocol's `shieldedMints`
|
|
50
|
+
* effect, so `_addMinted` adds accounting, not disclosure. See
|
|
51
|
+
* `NativeShieldedTokenSupplyCore` for the exact / lower-bound / upper-bound
|
|
52
|
+
* guarantees, the `burned <= minted` invariant, and the mint-side drift caveat.
|
|
53
|
+
*
|
|
54
|
+
* @warning Pair every mint with `_addMinted` and every burn with `_addBurned` on
|
|
55
|
+
* every path; mis-wiring is a security-critical, undetectable error (e.g. an
|
|
56
|
+
* undercounted mint makes `_addBurned` reject legitimate burns). See
|
|
57
|
+
* `NativeShieldedTokenSupplyCore`.
|
|
58
|
+
*/
|
|
59
|
+
module NativeShieldedTokenSupply {
|
|
60
|
+
import CompactStandardLibrary;
|
|
61
|
+
import "./NativeShieldedTokenSupplyCore" prefix Core_;
|
|
62
|
+
|
|
63
|
+
// Circuits use the `Core_` prefix above. The named import/re-export below is
|
|
64
|
+
// only for the core supply ledger keys, so the implementing contract's ledger
|
|
65
|
+
// keys read as `_totalMinted` / `_totalBurned` rather than
|
|
66
|
+
// `NativeShieldedTokenSupplyCore__totalMinted`. The circuit names are
|
|
67
|
+
// intentionally NOT imported unprefixed, to avoid potential clashes.
|
|
68
|
+
import { _totalMinted, _totalBurned } from "./NativeShieldedTokenSupplyCore";
|
|
69
|
+
export { _totalMinted, _totalBurned };
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @description The single fixed domain key. The paired `NativeShieldedToken`
|
|
73
|
+
* has exactly one token type, so all accounting lives under one key.
|
|
74
|
+
*/
|
|
75
|
+
circuit key(): Bytes<32> {
|
|
76
|
+
return default<Bytes<32>>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @description Adds `amount` to the exact minted total. Call once per
|
|
81
|
+
* successful `NativeShieldedToken._mint`.
|
|
82
|
+
*
|
|
83
|
+
* @circuitInfo k=9, rows=375
|
|
84
|
+
*
|
|
85
|
+
* @param {Uint<64>} amount - The minted amount.
|
|
86
|
+
* @return {[]} - Empty tuple.
|
|
87
|
+
*/
|
|
88
|
+
export circuit _addMinted(amount: Uint<64>): [] {
|
|
89
|
+
Core__addMinted(key(), amount);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @description Adds `amount` to the contract-mediated burned total. Call once
|
|
94
|
+
* per successful `NativeShieldedToken._burn` / `_burnFromSelf`.
|
|
95
|
+
* @dev Enforces `burned <= minted`; an unpaired or duplicate burn reverts.
|
|
96
|
+
*
|
|
97
|
+
* @circuitInfo k=9, rows=477
|
|
98
|
+
*
|
|
99
|
+
* @param {Uint<128>} amount - The burned amount.
|
|
100
|
+
* @return {[]} - Empty tuple.
|
|
101
|
+
*/
|
|
102
|
+
export circuit _addBurned(amount: Uint<128>): [] {
|
|
103
|
+
Core__addBurned(key(), amount);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @description Returns the exact amount ever minted.
|
|
108
|
+
*
|
|
109
|
+
* @circuitInfo k=7, rows=65
|
|
110
|
+
*
|
|
111
|
+
* @return {Uint<128>} - The total amount minted.
|
|
112
|
+
*/
|
|
113
|
+
export circuit totalMinted(): Uint<128> {
|
|
114
|
+
return Core_totalMinted(key());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @description Returns the contract-mediated amount burned.
|
|
119
|
+
* @notice This is a lower bound: coins sent directly to the burn address
|
|
120
|
+
* without going through the contract are not counted.
|
|
121
|
+
*
|
|
122
|
+
* @circuitInfo k=7, rows=62
|
|
123
|
+
*
|
|
124
|
+
* @return {Uint<128>} - The total amount burned through the contract.
|
|
125
|
+
*/
|
|
126
|
+
export circuit totalBurned(): Uint<128> {
|
|
127
|
+
return Core_totalBurned(key());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @description Returns `totalMinted() - totalBurned()`.
|
|
132
|
+
* @notice This is an UPPER BOUND on circulating supply, not an exact value:
|
|
133
|
+
* burns that bypass the contract are invisible. See the module notes.
|
|
134
|
+
*
|
|
135
|
+
* @circuitInfo k=9, rows=160
|
|
136
|
+
*
|
|
137
|
+
* @return {Uint<128>} - The upper bound on tokens in existence.
|
|
138
|
+
*/
|
|
139
|
+
export circuit totalSupply(): Uint<128> {
|
|
140
|
+
return Core_totalSupply(key());
|
|
141
|
+
}
|
|
142
|
+
}
|