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