@openzeppelin/compact-contracts 0.4.0 → 0.5.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 -4
- package/access/AccessControl.compact +8 -9
- package/access/Ownable.compact +21 -20
- package/access/ShieldedAccessControl.compact +20 -19
- package/access/ZOwnablePK.compact +5 -3
- package/crypto/Ecdh.compact +153 -0
- package/crypto/EcdhMask.compact +146 -84
- package/crypto/Ecdsa.compact +1 -1
- package/crypto/Eip712.compact +1 -1
- package/crypto/ElGamal.compact +13 -13
- package/math/UintN.compact +82 -0
- package/multisig/ForwarderPrivate.compact +6 -17
- package/multisig/ForwarderShielded.compact +1 -1
- package/multisig/ForwarderUnshielded.compact +1 -1
- package/multisig/ProposalManager.compact +127 -65
- package/multisig/Secp256k1EcdsaMultisig.compact +1 -1
- package/multisig/ShieldedTreasury.compact +8 -20
- package/multisig/ShieldedTreasuryStateless.compact +9 -21
- package/multisig/Signer.compact +1 -1
- package/multisig/UnshieldedTreasury.compact +11 -24
- package/multisig/examples/ForwarderPrivateExample.compact +2 -3
- package/multisig/examples/ForwarderShieldedExample.compact +1 -1
- package/multisig/examples/ForwarderUnshieldedExample.compact +1 -1
- package/multisig/examples/{ShieldedMultiSigV2Example.compact → NativeShieldedStatelessTreasuryExample.compact} +23 -23
- package/multisig/examples/NativeShieldedTokenIssuerExample.compact +1 -1
- package/multisig/presets/NativeShieldedStatelessTreasury.compact +443 -0
- package/multisig/presets/NativeShieldedTokenIssuer.compact +2 -5
- package/package.json +11 -11
- package/security/Allowlist.compact +1 -1
- package/security/Blocklist.compact +1 -1
- package/security/Initializable.compact +3 -1
- package/security/Pausable.compact +1 -1
- package/token/ConfidentialFungibleToken.compact +22 -70
- package/token/ConfidentialNoteFungibleToken.compact +741 -0
- package/token/FungibleToken.compact +36 -30
- package/token/MultiToken.compact +27 -21
- package/token/NativeShieldedToken.compact +4 -12
- package/token/NativeShieldedTokenCore.compact +1 -1
- package/token/NativeShieldedTokenFamily.compact +4 -4
- package/token/NonFungibleToken.compact +28 -22
- package/token/examples/ZOwnableNativeShieldedTokenExample.compact +1 -1
- package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
- package/token/extensions/ConfidentialNoteFungibleTokenDelivery.compact +274 -0
- package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
- package/token/extensions/NativeShieldedTokenFamilyPublicSupply.compact +1 -1
- package/token/extensions/NativeShieldedTokenPublicSupply.compact +1 -1
- package/token/extensions/NativeShieldedTokenPublicSupplyCore.compact +1 -1
- package/token/presets/ZOwnableNativeShieldedToken.compact +1 -1
- package/utils/EvmAbi.compact +1 -1
- package/utils/Utils.compact +1 -1
- package/multisig/RecipientTypes.compact +0 -48
- package/multisig/presets/ShieldedMultiSigV2.compact +0 -295
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/examples/NativeShieldedStatelessTreasuryExample.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @title
|
|
8
|
-
* @description Deployable reference contract for the `
|
|
7
|
+
* @title NativeShieldedStatelessTreasuryExample
|
|
8
|
+
* @description Deployable reference contract for the `NativeShieldedStatelessTreasury`
|
|
9
9
|
* preset: a 2-of-3 wallet whose spends are authorized by ECDSA signatures
|
|
10
10
|
* collected off-chain and verified in a single transaction.
|
|
11
11
|
*
|
|
@@ -15,14 +15,14 @@ pragma language_version >= 0.26.0;
|
|
|
15
15
|
|
|
16
16
|
import CompactStandardLibrary;
|
|
17
17
|
|
|
18
|
-
import "../presets/
|
|
18
|
+
import "../presets/NativeShieldedStatelessTreasury"<2> prefix NativeShieldedStatelessTreasury_;
|
|
19
19
|
|
|
20
20
|
export { ShieldedCoinInfo, QualifiedShieldedCoinInfo, ShieldedSendResult };
|
|
21
21
|
|
|
22
|
-
// Surface the preset's domain separator unprefixed so
|
|
23
|
-
// the key
|
|
24
|
-
import { _domainSeparator } from "../presets/
|
|
25
|
-
export { _domainSeparator };
|
|
22
|
+
// Surface the preset's replay counter and domain separator unprefixed so
|
|
23
|
+
// `ledger()` readers see the key names the preset published.
|
|
24
|
+
import { _nonce, _domainSeparator } from "../presets/NativeShieldedStatelessTreasury"<2>;
|
|
25
|
+
export { _nonce, _domainSeparator };
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* @description Deploys the wallet with 3 signer commitments.
|
|
@@ -34,42 +34,42 @@ constructor(
|
|
|
34
34
|
instanceSalt: Bytes<32>,
|
|
35
35
|
signerCommitments: Vector<3, Bytes<32>>
|
|
36
36
|
) {
|
|
37
|
-
|
|
37
|
+
NativeShieldedStatelessTreasury_initialize<3>(instanceSalt, signerCommitments);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export pure circuit _calculateSignerId(
|
|
41
|
+
pk: Secp256k1Point,
|
|
42
|
+
salt: Bytes<32>
|
|
43
|
+
): Bytes<32> {
|
|
44
|
+
return NativeShieldedStatelessTreasury__calculateSignerId(pk, salt);
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
export circuit deposit(coin: ShieldedCoinInfo): [] {
|
|
41
|
-
return
|
|
48
|
+
return NativeShieldedStatelessTreasury_deposit(coin);
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export circuit execute(
|
|
45
|
-
to:
|
|
52
|
+
to: NativeShieldedStatelessTreasury_Proposal_Recipient,
|
|
46
53
|
amount: Uint<128>,
|
|
47
54
|
coin: QualifiedShieldedCoinInfo,
|
|
48
55
|
pubkeys: Vector<2, Secp256k1Point>,
|
|
49
56
|
signatures: Vector<2, Secp256k1EcdsaSignature>
|
|
50
57
|
): ShieldedSendResult {
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export pure circuit _calculateSignerId(
|
|
55
|
-
pk: Secp256k1Point,
|
|
56
|
-
salt: Bytes<32>
|
|
57
|
-
): Bytes<32> {
|
|
58
|
-
return ShieldedMultiSigV2__calculateSignerId(pk, salt);
|
|
58
|
+
return NativeShieldedStatelessTreasury_execute(to, amount, coin, pubkeys, signatures);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export circuit getNonce(): Uint<64> {
|
|
62
|
-
return
|
|
62
|
+
return NativeShieldedStatelessTreasury_getNonce();
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export circuit getSignerCount(): Uint<8> {
|
|
66
|
-
return
|
|
66
|
+
return NativeShieldedStatelessTreasury_getSignerCount();
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export circuit getThreshold(): Uint<8> {
|
|
70
|
-
return
|
|
70
|
+
return NativeShieldedStatelessTreasury_getThreshold();
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export circuit isSigner(commitment: Bytes<32>): Boolean {
|
|
74
|
-
return
|
|
74
|
+
return NativeShieldedStatelessTreasury_isSigner(commitment);
|
|
75
75
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/examples/NativeShieldedTokenIssuerExample.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/presets/NativeShieldedStatelessTreasury.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.26.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedStatelessTreasury
|
|
8
|
+
* @description
|
|
9
|
+
* A threshold ECDSA multisig treasury for shielded coins. Anyone may
|
|
10
|
+
* deposit; value leaves only through `execute`, a single tx carrying `t`
|
|
11
|
+
* registered signers' approvals. There is no proposal lifecycle: signatures
|
|
12
|
+
* are collected off-chain and verified in that one tx.
|
|
13
|
+
*
|
|
14
|
+
* Composes `Secp256k1EcdsaMultisig` (signer commitments, approval checks),
|
|
15
|
+
* `ShieldedTreasuryStateless` as `Treasury_` (deposit and send, no coin
|
|
16
|
+
* state on the ledger) and `ProposalManager` as `Proposal_` for its
|
|
17
|
+
* `Recipient` and `RecipientKind` types alone.
|
|
18
|
+
*
|
|
19
|
+
* @dev Notation:
|
|
20
|
+
* - `H<T>(...)`: `persistentHash<T>` (SHA-256) over the Compact encoding of
|
|
21
|
+
* `T`, a `Bytes<32>` digest.
|
|
22
|
+
* - `keccak(...)`: `keccak256` over the concatenation of its 32-byte words.
|
|
23
|
+
* - `DS`: `_domainSeparator`, the EIP-712 domain separator fixed at
|
|
24
|
+
* construction.
|
|
25
|
+
* - `self`: `kernel.self().bytes`, this contract's address.
|
|
26
|
+
* - `nonce`: the `_nonce` value read at the start of `execute`, the
|
|
27
|
+
* signature replay counter.
|
|
28
|
+
* - `cm`: a signer commitment, the registry key.
|
|
29
|
+
*
|
|
30
|
+
* @dev Derivations:
|
|
31
|
+
* - Domain separator: `DS = Eip712_domainSeparator(
|
|
32
|
+
* keccak("NativeShieldedStatelessTreasury"), keccak("1"), instanceSalt)`,
|
|
33
|
+
* the EIP-712 domain `(name, version, salt)`. `chainId` and
|
|
34
|
+
* `verifyingContract` are absent: no network id is available in-circuit
|
|
35
|
+
* and a 32-byte address does not fit `address`. `self` inside the struct
|
|
36
|
+
* separates deployments instead.
|
|
37
|
+
* - Execute digest: `keccak(0x1901 ‖ DS ‖ keccak([EXECUTE_TYPEHASH, self,
|
|
38
|
+
* uint64Word(nonce), uint8Word(to.kind), to.address, coin.color,
|
|
39
|
+
* uint128Word(amount)]))`.
|
|
40
|
+
* - Type hash: `EXECUTE_TYPEHASH = keccak("Execute(bytes32 contractAddress,
|
|
41
|
+
* uint256 nonce,uint8 recipientKind,bytes32 recipient,bytes32 coinColor,
|
|
42
|
+
* uint256 amount)")`, written as a literal word.
|
|
43
|
+
* - Signer commitment: `cm = H<SignerCommitmentInput>({ pkX, pkY, salt,
|
|
44
|
+
* domain: pad(32, "Secp256k1EcdsaMultisig:signer") })`, `pkX` / `pkY`
|
|
45
|
+
* the affine coordinates as `Bytes<32>`. `_calculateSignerId` is the
|
|
46
|
+
* exported pure circuit.
|
|
47
|
+
* - Encoding: `H` hashes the Compact encoding, not a byte concatenation.
|
|
48
|
+
* Digest integers are big-endian ABI words (`utils/EvmAbi`), so an EVM
|
|
49
|
+
* signer's typed-data encoder reproduces them.
|
|
50
|
+
* `contracts/src/multisig/test/EcdsaTestUtils.ts` pins the digest through
|
|
51
|
+
* ethers (`executeMsgHash`).
|
|
52
|
+
*
|
|
53
|
+
* @notice Implementing contracts register the signer set from their
|
|
54
|
+
* constructor by calling this module's `initialize`, and MUST NOT import
|
|
55
|
+
* `Signer` or `Secp256k1EcdsaMultisig` directly.
|
|
56
|
+
*
|
|
57
|
+
* @dev Parameterization:
|
|
58
|
+
* `t` is the approval width and the threshold, fixed at import
|
|
59
|
+
* (`"../presets/NativeShieldedStatelessTreasury"<t>`): `execute` verifies
|
|
60
|
+
* exactly `t` distinct signers. The implementing contract fixes the signer
|
|
61
|
+
* count `n` at `initialize`. Import one width per contract: two widths share
|
|
62
|
+
* one signer registry, see `Secp256k1EcdsaMultisig`.
|
|
63
|
+
* `NativeShieldedStatelessTreasuryExample` fixes 2-of-3. The `@constraints`
|
|
64
|
+
* tag on `execute` is measured at `t = 2`.
|
|
65
|
+
*
|
|
66
|
+
* @dev Compatibility:
|
|
67
|
+
* The wire format the signers implement is the EIP-712 domain
|
|
68
|
+
* (`NativeShieldedStatelessTreasury`, version `1`, salt), the `Execute` type
|
|
69
|
+
* string, the `Secp256k1EcdsaMultisig:signer` commitment domain (owned by
|
|
70
|
+
* `Secp256k1EcdsaMultisig`), the `cm` preimage struct, and the `Vector<t>` /
|
|
71
|
+
* `Vector<n>` shapes the implementing contract fixes. Changing any of them
|
|
72
|
+
* breaks every existing signer setup. The test suite pins them through
|
|
73
|
+
* `EcdsaTestUtils.ts`.
|
|
74
|
+
*
|
|
75
|
+
* @notice Privacy:
|
|
76
|
+
* - Public, per tx:
|
|
77
|
+
* - Entry point: `deposit` or `execute`.
|
|
78
|
+
* - Approvers: the `t` signer commitments (`disclose(commitment)` in
|
|
79
|
+
* `Signer_assertSigner`), so WHICH signers approved.
|
|
80
|
+
* - Deposit: the coin commitment as a receive claim, with this contract
|
|
81
|
+
* named in the Zswap output (`disclose(coin)` in `Treasury__deposit`).
|
|
82
|
+
* - Execute: the spent coin's nullifier and the sent and change coin
|
|
83
|
+
* commitments. The returned `ShieldedSendResult` stays with the caller.
|
|
84
|
+
* - Public, as state:
|
|
85
|
+
* - Cells: `_nonce`, `_domainSeparator`, and the `Secp256k1EcdsaMultisig`
|
|
86
|
+
* cells (`_signers`, `_signerCount`, `_threshold`, `_instanceSalt`)
|
|
87
|
+
* behind its prefix. `_nonce` and `_domainSeparator` are exported, so an
|
|
88
|
+
* implementing contract can surface them in its `ledger()` reader, as
|
|
89
|
+
* the example does; the rest are read through the view circuits.
|
|
90
|
+
* - Absent: no coin map, no balances, no received or sent totals.
|
|
91
|
+
* - Private:
|
|
92
|
+
* - Signer material: public keys and signatures are verified in-circuit,
|
|
93
|
+
* never disclosed.
|
|
94
|
+
* - Operation data: the amount and the recipient.
|
|
95
|
+
* - Signer identity: keys sit behind `cm` with a public salt, so an observer
|
|
96
|
+
* holding a candidate key can test membership; one without cannot
|
|
97
|
+
* correlate a signer across deployments.
|
|
98
|
+
* - Auditability: no balance is stored, but a coin commitment is a hash of
|
|
99
|
+
* the coin's data, so whoever knows a held coin can link its receive to
|
|
100
|
+
* its later spend. The change coin's nonce derives from the spent coin's,
|
|
101
|
+
* so that observer can follow the change too.
|
|
102
|
+
*
|
|
103
|
+
* @notice Security:
|
|
104
|
+
* - Digest binding: the `Execute` struct binds its type hash, `self`,
|
|
105
|
+
* `nonce`, the recipient kind and address, `coin.color` and `amount` under
|
|
106
|
+
* `DS`, so a signature authorizes one spend on one instance at one nonce,
|
|
107
|
+
* and is neither replayable nor redirectable. Removing the `self` word
|
|
108
|
+
* removes cross-instance replay protection, since `DS` cannot carry the
|
|
109
|
+
* address.
|
|
110
|
+
* - Low-s: verification goes through `Ecdsa_secp256k1EcdsaVerifyLowS`, so a
|
|
111
|
+
* high-s encoding is an invalid signature.
|
|
112
|
+
* - Distinct signers: `assertApprovals` takes exactly `t` keys and rejects a
|
|
113
|
+
* repeated commitment, so no set of fewer than `t` registered signers
|
|
114
|
+
* moves value. `initialize` rejects `n < t`.
|
|
115
|
+
* - Recipient kind: `execute` rejects any recipient but a `ShieldedUser`. A
|
|
116
|
+
* contract-addressed coin would be unclaimable, making the transaction
|
|
117
|
+
* malformed.
|
|
118
|
+
* - Open deposits: `deposit` has no access control, by design. Only
|
|
119
|
+
* `execute` moves value out.
|
|
120
|
+
* - No coin bookkeeping: with no coin registry, a double spend or a coin the
|
|
121
|
+
* contract does not own is rejected by the protocol at application time
|
|
122
|
+
* rather than by an assert here. Sufficiency (`coin.value >= amount`) is
|
|
123
|
+
* likewise left to `sendShielded`.
|
|
124
|
+
* - Immutable signer set: no signer reconfiguration circuit is exported, so
|
|
125
|
+
* the commitments and the threshold are fixed. Losing more than `n - t`
|
|
126
|
+
* keys freezes spending.
|
|
127
|
+
* - Atomicity: a failed assert rejects the whole tx before any state moves;
|
|
128
|
+
* the nonce does not advance and the sender pays no fee.
|
|
129
|
+
*
|
|
130
|
+
* @notice Concurrency:
|
|
131
|
+
* - Replay nonce: `execute` reads `_nonce` into the digest. The read is
|
|
132
|
+
* pinned, so any two executes in one block conflict: at most one lands per
|
|
133
|
+
* block, and the losing signers re-sign at the next nonce.
|
|
134
|
+
* - `getNonce`: submitted as a tx it pins `_nonce` and conflicts with a
|
|
135
|
+
* same-block `execute`.
|
|
136
|
+
* - Deposits: `deposit` writes no ledger state, so it commutes with
|
|
137
|
+
* everything, a same-block `execute` included.
|
|
138
|
+
* - Views: the signer views read only state written by `initialize`, so they
|
|
139
|
+
* commute with everything.
|
|
140
|
+
*
|
|
141
|
+
* @notice Scope:
|
|
142
|
+
* Deposit and send only. No mint, no burn, no unshielded rail, and no
|
|
143
|
+
* on-chain proposal: `ProposalManager` is imported for `Proposal_Recipient`
|
|
144
|
+
* and `Proposal_RecipientKind` only, while its `_proposals` map and
|
|
145
|
+
* `_nextProposalId` are allocated by the import and never written. `execute`
|
|
146
|
+
* checks `to.kind` itself rather than calling `toShieldedRecipient`. Coins
|
|
147
|
+
* the treasury holds are ordinary Zswap coins it neither tracks nor
|
|
148
|
+
* restricts.
|
|
149
|
+
*
|
|
150
|
+
* @notice Client-side duties:
|
|
151
|
+
* - Before deploy: pick a random `instanceSalt`, derive each `cm` with
|
|
152
|
+
* `_calculateSignerId(pk, salt)`, pass them as `signerCommitments`.
|
|
153
|
+
* - Per operation: read `getNonce()`, rebuild the digest as
|
|
154
|
+
* `EcdsaTestUtils.ts` does, collect `t` low-s signatures off-chain, submit
|
|
155
|
+
* keys and signatures in matching `Vector<t>` positions.
|
|
156
|
+
* - Coin tracking: keep the treasury's coins off-chain, recovering each
|
|
157
|
+
* coin's `mt_index` from the indexer's `ZswapOutput` / ledger events to
|
|
158
|
+
* build the `QualifiedShieldedCoinInfo` that `execute` takes.
|
|
159
|
+
* - Change coin: persist the one `execute` returns. `sendShielded` emits it
|
|
160
|
+
* as a fresh self-owned output, and it replaces the spent coin as the
|
|
161
|
+
* treasury's holding.
|
|
162
|
+
*
|
|
163
|
+
* @notice Scalability:
|
|
164
|
+
* - Throughput: one `execute` per block (see Concurrency). Deposits write no
|
|
165
|
+
* ledger state, so they do not contend.
|
|
166
|
+
* - State: constant-size, one counter, the domain separator and an
|
|
167
|
+
* `n`-entry signer set. Nothing grows per deposit or spend.
|
|
168
|
+
* - Coins: no merge circuit, so the off-chain pool gains one coin per
|
|
169
|
+
* deposit and `execute` spends exactly one of them. A spend larger than
|
|
170
|
+
* the largest single coin is not expressible.
|
|
171
|
+
* - Amounts: `execute` takes `Uint<128>`.
|
|
172
|
+
*
|
|
173
|
+
* @dev Composition:
|
|
174
|
+
* - On-chain balances: swap `ShieldedTreasuryStateless` for
|
|
175
|
+
* `ShieldedTreasury` to get one merged UTXO per color and cumulative
|
|
176
|
+
* totals, both published.
|
|
177
|
+
* - Unshielded rail: pair `Proposal_toUnshieldedRecipient` with
|
|
178
|
+
* `UnshieldedTreasury`; this preset wires only the shielded one.
|
|
179
|
+
* - Other thresholds: import at another `t` (see Parameterization).
|
|
180
|
+
* - Proposal lifecycle: wire in the rest of `ProposalManager` for on-chain
|
|
181
|
+
* proposals, at the cost its own `@warning` names: every proposal's
|
|
182
|
+
* recipient, color and amount becomes public.
|
|
183
|
+
*
|
|
184
|
+
* @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
|
|
185
|
+
*/
|
|
186
|
+
module NativeShieldedStatelessTreasury<#t> {
|
|
187
|
+
import CompactStandardLibrary;
|
|
188
|
+
|
|
189
|
+
import "../Secp256k1EcdsaMultisig"<t> prefix Secp256k1EcdsaMultisig_;
|
|
190
|
+
import "../../crypto/Eip712" prefix Eip712_;
|
|
191
|
+
import "../../utils/EvmAbi" prefix EvmAbi_;
|
|
192
|
+
import "../ProposalManager" prefix Proposal_;
|
|
193
|
+
import "../ShieldedTreasuryStateless" prefix Treasury_;
|
|
194
|
+
|
|
195
|
+
// Implementing contracts need this for the re-exported `execute` signature.
|
|
196
|
+
// Importing `ProposalManager` again instead would give them a second,
|
|
197
|
+
// unwritten copy of its state.
|
|
198
|
+
export { Proposal_Recipient };
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @description Signature replay counter. `execute` reads it into its
|
|
202
|
+
* digest and increments it; `getNonce` returns the value the next digest
|
|
203
|
+
* binds to. The read is pinned, so at most one `execute` lands per block.
|
|
204
|
+
* Exported so the implementing contract's `ledger()` reader can surface it.
|
|
205
|
+
*/
|
|
206
|
+
export ledger _nonce: Counter;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @description The EIP-712 domain separator every `execute` digest is built
|
|
210
|
+
* under, derived from `(name, version, instanceSalt)`. Set once at
|
|
211
|
+
* construction.
|
|
212
|
+
*/
|
|
213
|
+
export sealed ledger _domainSeparator: Bytes<32>;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @description Registers the signer set at threshold `t` and fixes the
|
|
217
|
+
* EIP-712 domain separator.
|
|
218
|
+
*
|
|
219
|
+
* @notice Call from the implementing contract's constructor only. It writes
|
|
220
|
+
* the `sealed` `_domainSeparator`, so implementing contracts MUST NOT expose
|
|
221
|
+
* `initialize` as a post-deploy circuit.
|
|
222
|
+
*
|
|
223
|
+
* The EIP-712 domain fixed here is `(name, version, salt)`. `chainId` and
|
|
224
|
+
* `verifyingContract` are absent: no network identifier is available
|
|
225
|
+
* in-circuit, and a 32-byte Midnight address does not fit
|
|
226
|
+
* `verifyingContract`'s `address` type.
|
|
227
|
+
*
|
|
228
|
+
* Replay protection comes from `execute` binding `kernel.self()` in its
|
|
229
|
+
* struct rather than from the domain. The ledger randomizes a contract's
|
|
230
|
+
* address at deployment, so no two deployments share one on any network.
|
|
231
|
+
* A signature for one cannot authorize another.
|
|
232
|
+
*
|
|
233
|
+
* `instanceSalt` also keys the signer commitments, so a fresh value per
|
|
234
|
+
* deployment keeps one public key from producing the same commitment in two
|
|
235
|
+
* deployments which would link the signer set across them.
|
|
236
|
+
*
|
|
237
|
+
* Requirements:
|
|
238
|
+
*
|
|
239
|
+
* - `instanceSalt` should be cryptographically random.
|
|
240
|
+
* - `signerCommitments` must not contain duplicates.
|
|
241
|
+
* - `n` must be at least `t`.
|
|
242
|
+
*
|
|
243
|
+
* @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
|
|
244
|
+
* @param {Vector<n, Bytes<32>>} signerCommitments - Hashed signer identities.
|
|
245
|
+
* @returns {[]} Empty tuple.
|
|
246
|
+
*/
|
|
247
|
+
export circuit initialize<#n>(
|
|
248
|
+
instanceSalt: Bytes<32>,
|
|
249
|
+
signerCommitments: Vector<n, Bytes<32>>
|
|
250
|
+
): [] {
|
|
251
|
+
Secp256k1EcdsaMultisig_initialize<n>(instanceSalt, signerCommitments);
|
|
252
|
+
|
|
253
|
+
const hashedName = keccak256<Bytes<31>>(pad(31, "NativeShieldedStatelessTreasury"));
|
|
254
|
+
const hashedVersion = keccak256<Bytes<1>>(pad(1, "1"));
|
|
255
|
+
_domainSeparator = Eip712_domainSeparator(
|
|
256
|
+
hashedName,
|
|
257
|
+
hashedVersion,
|
|
258
|
+
disclose(instanceSalt)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @description `keccak256` of the `Execute` type string:
|
|
264
|
+
* `Execute(bytes32 contractAddress,uint256 nonce,uint8 recipientKind,bytes32 recipient,bytes32 coinColor,uint256 amount)`
|
|
265
|
+
*
|
|
266
|
+
* @dev Written as a literal word. `compactc` emits a full keccak
|
|
267
|
+
* permutation even for a constant argument, so hashing the type string
|
|
268
|
+
* in-circuit would add ~4.2k rows to every `execute`. The spec checks this
|
|
269
|
+
* word against the type string above.
|
|
270
|
+
*
|
|
271
|
+
* @returns {Bytes<32>} The `Execute` type hash.
|
|
272
|
+
*/
|
|
273
|
+
pure circuit EXECUTE_TYPEHASH(): Bytes<32> {
|
|
274
|
+
const typeHash: Vector<32, Uint<8>> = [
|
|
275
|
+
0x8e, 0x36, 0xc9, 0x78, 0xf2, 0x00, 0x15, 0xe8,
|
|
276
|
+
0x1d, 0x7a, 0x53, 0xef, 0xc3, 0x2e, 0x2b, 0x08,
|
|
277
|
+
0xf1, 0x24, 0x97, 0xa5, 0x05, 0x7c, 0x70, 0x20,
|
|
278
|
+
0x7f, 0x8b, 0xc4, 0xb7, 0xc6, 0xcf, 0x5e, 0x15
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
return typeHash as Bytes<32>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @description Computes a signer commitment from a secp256k1 public key,
|
|
286
|
+
* as `persistentHash(pkX, pkY, salt, "Secp256k1EcdsaMultisig:signer")`.
|
|
287
|
+
*
|
|
288
|
+
* Pure, so the deployer can derive constructor commitments off-chain.
|
|
289
|
+
*
|
|
290
|
+
* Requirements:
|
|
291
|
+
*
|
|
292
|
+
* - `pk` must not be the secp256k1 identity point, which has no coordinates.
|
|
293
|
+
*
|
|
294
|
+
* @param {Secp256k1Point} pk - The secp256k1 public key.
|
|
295
|
+
* @param {Bytes<32>} salt - The instance salt.
|
|
296
|
+
*
|
|
297
|
+
* @returns {Bytes<32>} The signer commitment.
|
|
298
|
+
*/
|
|
299
|
+
export pure circuit _calculateSignerId(
|
|
300
|
+
pk: Secp256k1Point,
|
|
301
|
+
salt: Bytes<32>
|
|
302
|
+
): Bytes<32> {
|
|
303
|
+
return Secp256k1EcdsaMultisig_calculateSignerId(pk, salt);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @description Receives a shielded coin into the multisig treasury.
|
|
308
|
+
*
|
|
309
|
+
* No access control which allows anyone to deposit. The coin is claimed at the
|
|
310
|
+
* protocol level through receiveShielded. No coin data is stored on the
|
|
311
|
+
* public ledger, preserving full balance privacy.
|
|
312
|
+
*
|
|
313
|
+
* The operator discovers the coin's Merkle tree index by subscribing
|
|
314
|
+
* to ZswapOutput events via the indexer, filtering by contract address,
|
|
315
|
+
* and extracting mt_index. Combined with the known ShieldedCoinInfo,
|
|
316
|
+
* this produces the QualifiedShieldedCoinInfo needed for spending.
|
|
317
|
+
*
|
|
318
|
+
* @constraints k=13, rows=5898 (zkir-v3)
|
|
319
|
+
*
|
|
320
|
+
* @param {ShieldedCoinInfo} coin - The incoming shielded coin.
|
|
321
|
+
*/
|
|
322
|
+
export circuit deposit(coin: ShieldedCoinInfo): [] {
|
|
323
|
+
Treasury__deposit(coin);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @description Executes a shielded send authorized by threshold signatures.
|
|
328
|
+
*
|
|
329
|
+
* The circuit reads the current nonce from the ledger, increments it,
|
|
330
|
+
* then reconstructs the EIP-712 digest that signers must have signed
|
|
331
|
+
* off-chain: the `Execute` struct (contract address, nonce, recipient kind,
|
|
332
|
+
* recipient, coin color, amount) hashed under this deployment's domain
|
|
333
|
+
* separator. The digest is bound to the contract instance and the full recipient (kind and
|
|
334
|
+
* address), so a signature authorizes exactly one operation on one instance.
|
|
335
|
+
*
|
|
336
|
+
* Approvals are checked by `Secp256k1EcdsaMultisig`, which derives each
|
|
337
|
+
* signer's commitment from their public key and the instance salt and
|
|
338
|
+
* rejects duplicates. Constraints below are measured at `t = 2`.
|
|
339
|
+
*
|
|
340
|
+
* @notice `to` must be a `ShieldedUser`. A contract-addressed coin would be
|
|
341
|
+
* unclaimable, making the transaction malformed.
|
|
342
|
+
*
|
|
343
|
+
* @constraints k=17, rows=98969 (zkir-v3)
|
|
344
|
+
*
|
|
345
|
+
* Requirements:
|
|
346
|
+
*
|
|
347
|
+
* - No two entries may be the same signer.
|
|
348
|
+
* - Every public key must hash to a registered signer commitment.
|
|
349
|
+
* - Every signature must be valid over the message hash and use the low-s
|
|
350
|
+
* encoding.
|
|
351
|
+
* - `to.kind` must be `ShieldedUser`.
|
|
352
|
+
* - Coin value must be >= amount.
|
|
353
|
+
*
|
|
354
|
+
* @param {Proposal_Recipient} to - The recipient.
|
|
355
|
+
* @param {Uint<128>} amount - The amount to send.
|
|
356
|
+
* @param {QualifiedShieldedCoinInfo} coin - The coin to spend (from operator's pool).
|
|
357
|
+
* @param {Vector<t, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
358
|
+
* @param {Vector<t, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the operation.
|
|
359
|
+
*
|
|
360
|
+
* @returns {ShieldedSendResult} The send result including any change.
|
|
361
|
+
*/
|
|
362
|
+
export circuit execute(
|
|
363
|
+
to: Proposal_Recipient,
|
|
364
|
+
amount: Uint<128>,
|
|
365
|
+
coin: QualifiedShieldedCoinInfo,
|
|
366
|
+
pubkeys: Vector<t, Secp256k1Point>,
|
|
367
|
+
signatures: Vector<t, Secp256k1EcdsaSignature>
|
|
368
|
+
): ShieldedSendResult {
|
|
369
|
+
// Increment nonce
|
|
370
|
+
const currentNonce = _nonce;
|
|
371
|
+
_nonce.increment(1);
|
|
372
|
+
|
|
373
|
+
// Construct message hash
|
|
374
|
+
const msgHash = Eip712_hashTypedData(_domainSeparator, keccak256<Vector<7, Bytes<32>>>([
|
|
375
|
+
EXECUTE_TYPEHASH(),
|
|
376
|
+
// Binds the digest to this deployment. The domain cannot carry the
|
|
377
|
+
// address, so removing this word removes cross-instance replay
|
|
378
|
+
// protection.
|
|
379
|
+
kernel.self().bytes,
|
|
380
|
+
EvmAbi_uint64Word(currentNonce),
|
|
381
|
+
EvmAbi_uint8Word(to.kind as Uint<8>),
|
|
382
|
+
to.address,
|
|
383
|
+
coin.color,
|
|
384
|
+
EvmAbi_uint128Word(amount)
|
|
385
|
+
]));
|
|
386
|
+
|
|
387
|
+
Secp256k1EcdsaMultisig_assertApprovals(msgHash, pubkeys, signatures);
|
|
388
|
+
|
|
389
|
+
assert(
|
|
390
|
+
to.kind == Proposal_RecipientKind.ShieldedUser,
|
|
391
|
+
"NativeShieldedStatelessTreasury: recipient must be a coin public key"
|
|
392
|
+
);
|
|
393
|
+
const recipient = left<ZswapCoinPublicKey, ContractAddress>(
|
|
394
|
+
ZswapCoinPublicKey { bytes: to.address }
|
|
395
|
+
);
|
|
396
|
+
return Treasury__send(coin, recipient, amount);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @description Returns the nonce the next `execute` digest will bind to.
|
|
401
|
+
*
|
|
402
|
+
* @constraints k=6, rows=34 (zkir-v3)
|
|
403
|
+
*
|
|
404
|
+
* @returns {Uint<64>} The current nonce.
|
|
405
|
+
*/
|
|
406
|
+
export circuit getNonce(): Uint<64> {
|
|
407
|
+
return _nonce;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @description Returns the number of registered signers.
|
|
412
|
+
*
|
|
413
|
+
* @constraints k=6, rows=34 (zkir-v3)
|
|
414
|
+
*
|
|
415
|
+
* @returns {Uint<8>} The signer count.
|
|
416
|
+
*/
|
|
417
|
+
export circuit getSignerCount(): Uint<8> {
|
|
418
|
+
return Secp256k1EcdsaMultisig_getSignerCount();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @description Returns the approval threshold.
|
|
423
|
+
*
|
|
424
|
+
* @constraints k=6, rows=34 (zkir-v3)
|
|
425
|
+
*
|
|
426
|
+
* @returns {Uint<8>} The threshold.
|
|
427
|
+
*/
|
|
428
|
+
export circuit getThreshold(): Uint<8> {
|
|
429
|
+
return Secp256k1EcdsaMultisig_getThreshold();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @description Returns whether the given commitment is a registered signer.
|
|
434
|
+
*
|
|
435
|
+
* @constraints k=8, rows=129 (zkir-v3)
|
|
436
|
+
*
|
|
437
|
+
* @param {Bytes<32>} commitment - The signer commitment to check.
|
|
438
|
+
* @returns {Boolean} True if registered.
|
|
439
|
+
*/
|
|
440
|
+
export circuit isSigner(commitment: Bytes<32>): Boolean {
|
|
441
|
+
return Secp256k1EcdsaMultisig_isSigner(commitment);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/presets/NativeShieldedTokenIssuer.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -183,10 +183,7 @@ pragma language_version >= 0.26.0;
|
|
|
183
183
|
* Mint and burn only. No send, transfer or execute circuit exists, so
|
|
184
184
|
* contract-held coins (from `mintToSelf` or `burnFromSelf` change) leave only
|
|
185
185
|
* through `burnFromSelf`, supplied by the operator as
|
|
186
|
-
* `QualifiedShieldedCoinInfo`.
|
|
187
|
-
* custody module composed by the consumer that records the coin without
|
|
188
|
-
* claiming it a second time; `ShieldedTreasury._deposit` is not that module
|
|
189
|
-
* today (compact-contracts#1112). Coins minted to users are ordinary
|
|
186
|
+
* `QualifiedShieldedCoinInfo`. Coins minted to users are ordinary
|
|
190
187
|
* Zswap coins the contract neither tracks nor restricts; a holder redeems by
|
|
191
188
|
* paying the coin into a `burn` tx. No supply accounting.
|
|
192
189
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openzeppelin/compact-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-alpha.2",
|
|
4
4
|
"description": "OpenZeppelin Compact contract library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openzeppelin",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"#test-utils/*": "./test-utils/*"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"compile": "compact-compiler
|
|
28
|
-
"compile:
|
|
29
|
-
"compile:
|
|
30
|
-
"compile:
|
|
31
|
-
"compile:multisig": "compact-compiler --dir multisig --feature-zkir-v3",
|
|
32
|
-
"compile:security": "compact-compiler --dir security",
|
|
33
|
-
"compile:token": "compact-compiler --dir token",
|
|
34
|
-
"compile:utils": "compact-compiler --dir utils",
|
|
35
|
-
"compile:integration": "compact-compiler --src test/integration/_mocks",
|
|
27
|
+
"compile:access": "rm -rf artifacts/access && compact-compiler +0.34.0 --hierarchical --dir access",
|
|
28
|
+
"compile:archive": "rm -rf artifacts/archive && compact-compiler +0.34.0 --hierarchical --dir archive",
|
|
29
|
+
"compile:crypto": "rm -rf artifacts/crypto && compact-compiler +0.34.0 --hierarchical --dir crypto --feature-zkir-v3 --exclude 'MockElGamal.compact' --exclude 'MockEcdhMask.compact' --exclude 'MockCurveOps.compact' && compact-compiler +0.34.0 --hierarchical --dir crypto/test/mocks --exclude 'MockEcdsa.compact' --exclude 'MockEip712.compact'",
|
|
30
|
+
"compile:math": "rm -rf artifacts/math && compact-compiler +0.34.0 --hierarchical --dir math",
|
|
31
|
+
"compile:multisig": "rm -rf artifacts/multisig && compact-compiler +0.34.0 --hierarchical --dir multisig --feature-zkir-v3",
|
|
32
|
+
"compile:security": "rm -rf artifacts/security && compact-compiler +0.34.0 --hierarchical --dir security",
|
|
33
|
+
"compile:token": "rm -rf artifacts/token && compact-compiler +0.34.0 --hierarchical --dir token",
|
|
34
|
+
"compile:utils": "rm -rf artifacts/utils && compact-compiler +0.34.0 --hierarchical --dir utils",
|
|
35
|
+
"compile:integration": "rm -rf artifacts/integration && compact-compiler +0.34.0 --hierarchical --src test/integration/_mocks --out artifacts/integration",
|
|
36
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",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@vitest/coverage-v8": "^5.0.0",
|
|
72
72
|
"ethers": "^6.17.0",
|
|
73
73
|
"fast-check": "^4.10.1",
|
|
74
|
-
"pino": "^
|
|
74
|
+
"pino": "^10.3.1",
|
|
75
75
|
"ts-node": "^10.9.2",
|
|
76
76
|
"typescript": "^6.0.3",
|
|
77
77
|
"vite": "^8.3.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (security/Initializable.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -35,6 +35,8 @@ 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
|
+
*
|
|
38
40
|
* Requirements:
|
|
39
41
|
*
|
|
40
42
|
* - Contract must not be initialized.
|