@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
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.4.0-alpha.2 (multisig/presets/NativeShieldedTokenIssuer.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.26.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenIssuer
|
|
8
|
+
* @description
|
|
9
|
+
* A 2-of-3 ECDSA multisig that mints and burns one native shielded token,
|
|
10
|
+
* for tokenized deposits. Value enters only through `mint` and leaves the
|
|
11
|
+
* contract only through `burn`; each is a single tx carrying two registered
|
|
12
|
+
* signers' approvals, so no single party creates or destroys tokens.
|
|
13
|
+
*
|
|
14
|
+
* Composes `EcdsaSignerManager` (signer commitments, approval checks),
|
|
15
|
+
* `NativeShieldedToken` as `NST_` (one token; domain, `name`, `symbol` and
|
|
16
|
+
* `decimals` fixed at construction) and `NativeShieldedTokenDerivedNonce` as
|
|
17
|
+
* `NSTDerivedNonce_` (coin nonces derived from a counter).
|
|
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
|
+
* - `opNonce`: the `_counter` value read at the start of `mint` / `burn`. It
|
|
27
|
+
* is the signature replay counter and is separate from the
|
|
28
|
+
* `NativeShieldedTokenDerivedNonce` counter that feeds coin nonces.
|
|
29
|
+
* - `cm`: a signer commitment, the registry key.
|
|
30
|
+
* - `color`: `tokenType(_domain, self)`, this token's coin color.
|
|
31
|
+
*
|
|
32
|
+
* @dev Derivations:
|
|
33
|
+
* - Domain separator: `DS = Eip712_domainSeparator(
|
|
34
|
+
* keccak("NativeShieldedTokenIssuer"), keccak("1"), instanceSalt)`, the
|
|
35
|
+
* EIP-712 domain `(name, version, salt)`. `chainId` and
|
|
36
|
+
* `verifyingContract` are absent: no network id is available in-circuit
|
|
37
|
+
* and a 32-byte address does not fit `address`. `self` inside each struct
|
|
38
|
+
* separates deployments instead.
|
|
39
|
+
* - Mint digest: `keccak(0x1901 ‖ DS ‖ keccak([MINT_TYPEHASH, self,
|
|
40
|
+
* recipientWord, boolWord(isContract), uint64Word(opNonce),
|
|
41
|
+
* uint64Word(amount)]))`, with `canonRecipient =
|
|
42
|
+
* Utils_canonicalize(recipient)` (unused `Either` arm zeroed),
|
|
43
|
+
* `recipientWord` its active arm and `isContract = !canonRecipient.is_left`.
|
|
44
|
+
* - Burn digest: `keccak(0x1901 ‖ DS ‖ keccak([BURN_TYPEHASH, self,
|
|
45
|
+
* uint64Word(opNonce), uint64Word(amount)]))`.
|
|
46
|
+
* - Type hashes: `MINT_TYPEHASH = keccak("Mint(bytes32 contractAddress,
|
|
47
|
+
* bytes32 recipient,bool isContract,uint256 nonce,uint256 amount)")` and
|
|
48
|
+
* `BURN_TYPEHASH = keccak("Burn(bytes32 contractAddress,uint256 nonce,
|
|
49
|
+
* uint256 amount)")`, written as literal words.
|
|
50
|
+
* - Signer commitment: `cm = H<SignerCommitmentInput>({ pkX, pkY, salt,
|
|
51
|
+
* domain: pad(32, "multisig:signer:") })`, `pkX` / `pkY` the affine
|
|
52
|
+
* coordinates as `Bytes<32>`. `_calculateSignerId` is the exported pure
|
|
53
|
+
* circuit.
|
|
54
|
+
* - Coin nonce: `evolveNonce(counter, pad(32, "NativeShieldedToken:nonce"))`
|
|
55
|
+
* over the derived-nonce counter after its increment.
|
|
56
|
+
* - Encoding: `H` hashes the Compact encoding, not a byte concatenation.
|
|
57
|
+
* Digest integers are big-endian ABI words (`utils/EvmAbi`), so an EVM
|
|
58
|
+
* signer's typed-data encoder reproduces them.
|
|
59
|
+
* `contracts/src/multisig/test/EcdsaTestUtils.ts` pins the digests
|
|
60
|
+
* through ethers (`mintMsgHash`, `burnMsgHash`).
|
|
61
|
+
*
|
|
62
|
+
* @notice Implementing contracts register the signer set from their
|
|
63
|
+
* constructor by calling this module's `initialize`, and MUST NOT import
|
|
64
|
+
* `EcdsaSignerManager` directly.
|
|
65
|
+
*
|
|
66
|
+
* @dev Parameterization:
|
|
67
|
+
* None: the signer set is hardcoded to 3 and the threshold to 2.
|
|
68
|
+
*
|
|
69
|
+
* @dev Compatibility:
|
|
70
|
+
* The wire format the custodian's signer implements is the EIP-712 domain
|
|
71
|
+
* (`NativeShieldedTokenIssuer`, version `1`, salt), the `Mint` / `Burn`
|
|
72
|
+
* type strings, the `multisig:signer:` commitment domain (generic prefix,
|
|
73
|
+
* owned by `EcdsaSignerManager`), the `cm` preimage struct, and the
|
|
74
|
+
* `Vector<2>` / `Vector<3>` shapes. Changing any of them breaks every
|
|
75
|
+
* existing signer setup. The test suite pins them through
|
|
76
|
+
* `EcdsaTestUtils.ts`.
|
|
77
|
+
*
|
|
78
|
+
* @notice Privacy:
|
|
79
|
+
* - Public, per tx:
|
|
80
|
+
* - Entry point: mint vs burn.
|
|
81
|
+
* - Approvers: the two signer commitments (`disclose(commitment)` in
|
|
82
|
+
* `Signer_assertSigner`), so WHICH two of the three signers approved.
|
|
83
|
+
* - Mint: `amount`, as the `shieldedMints` effect under the token domain,
|
|
84
|
+
* and the coin commitment as a spend claim. Recipient and nonce are not
|
|
85
|
+
* published. The nonce is `evolveNonce` of the public
|
|
86
|
+
* `_derivedNonceCounter`, so a candidate recipient can be tested
|
|
87
|
+
* against the commitment. A mint to this contract adds the same
|
|
88
|
+
* commitment as a receive claim and names the contract in the Zswap
|
|
89
|
+
* output.
|
|
90
|
+
* - Burn: the spent coin's nullifier, the burn-output commitment and, on a
|
|
91
|
+
* partial burn, the change commitment as both spend and receive claim.
|
|
92
|
+
* `amount`, the coin's value and `mt_index` are not published. Every
|
|
93
|
+
* coin this contract holds was minted to it, so its nullifier is
|
|
94
|
+
* computable and a burn links to its mint; the amount is protected by
|
|
95
|
+
* the commitment hash alone.
|
|
96
|
+
* - Public, as state:
|
|
97
|
+
* - Cells: `_counter`, `_domainSeparator`, `_derivedNonceCounter`,
|
|
98
|
+
* `_signers`, `_signerCount`, `_threshold`, `_instanceSalt`, `_domain`,
|
|
99
|
+
* `_name` / `_symbol` / `_decimals`, `_isInitialized`. All are exported, so an implementing
|
|
100
|
+
* contract can surface them in its `ledger()` reader.
|
|
101
|
+
* - Absent: no supply, no balances, no coin registry.
|
|
102
|
+
* - Private:
|
|
103
|
+
* - Signer material: public keys and signatures are verified in-circuit,
|
|
104
|
+
* never disclosed.
|
|
105
|
+
* - Operation data: burn amounts. Mint recipients, up to the commitment
|
|
106
|
+
* test above.
|
|
107
|
+
* - Signer identity: keys sit behind `cm` with a public salt, so an observer
|
|
108
|
+
* holding a candidate key can test membership; one without cannot
|
|
109
|
+
* correlate a signer across deployments.
|
|
110
|
+
* - Counterparty: a mint recipient learns nothing beyond the coin handed over
|
|
111
|
+
* out of band; every observer learns the amount of every mint and can
|
|
112
|
+
* test a candidate recipient against the commitment.
|
|
113
|
+
* - Auditability: mint amounts are public, burn amounts are not. No
|
|
114
|
+
* on-chain total is kept. `NativeShieldedTokenPublicSupply` adds one and
|
|
115
|
+
* makes burn amounts public.
|
|
116
|
+
*
|
|
117
|
+
* @notice Security:
|
|
118
|
+
* - Digest binding: each struct binds its type hash, `self`, `opNonce` and
|
|
119
|
+
* `amount` (`mint` also the recipient word and its kind) under `DS`, so a
|
|
120
|
+
* signature authorizes one operation on one instance at one nonce, and is
|
|
121
|
+
* neither replayable as the other operation, nor under another domain,
|
|
122
|
+
* nor redirectable. Removing the `self` word removes cross-instance
|
|
123
|
+
* replay protection, since `DS` cannot carry the address.
|
|
124
|
+
* - Low-s: verification goes through `Ecdsa_secp256k1EcdsaVerifyLowS`, so a
|
|
125
|
+
* high-s encoding is an invalid signature.
|
|
126
|
+
* - Distinct signers: `assertApprovals` asserts both entries and rejects a
|
|
127
|
+
* repeated commitment, so two distinct registered signers are always
|
|
128
|
+
* required. The threshold `2` is the ceiling `EcdsaSignerManager` accepts.
|
|
129
|
+
* - Canonical recipient: `Utils_canonicalize` zeroes the unused `Either` arm
|
|
130
|
+
* before hashing and minting, so a recipient with both arms populated
|
|
131
|
+
* cannot yield two digests for one destination.
|
|
132
|
+
* - Immutable signer set: no signer reconfiguration circuit is exported, so
|
|
133
|
+
* the three commitments and the threshold are fixed. Losing two of the
|
|
134
|
+
* three keys freezes the contract.
|
|
135
|
+
* - Griefing: the derived-nonce mint is the only mint path and it is
|
|
136
|
+
* threshold-gated, so `NativeShieldedTokenDerivedNonce`'s
|
|
137
|
+
* collision-griefing precondition (a caller-nonce mint at another trust
|
|
138
|
+
* level) is absent.
|
|
139
|
+
* - Atomicity: a failed assert rejects the whole tx before any state moves;
|
|
140
|
+
* neither counter advances and the sender pays no fee.
|
|
141
|
+
*
|
|
142
|
+
* @notice Concurrency:
|
|
143
|
+
* - Replay counter: `mint` and `burn` both read `_counter` into the digest.
|
|
144
|
+
* The read is pinned, so any two of them in one block conflict: at most one
|
|
145
|
+
* mint-or-burn lands per block, and the losing signer pair re-signs at the
|
|
146
|
+
* next nonce.
|
|
147
|
+
* - Derived-nonce counter: `mint` also pins it (`evolveNonce` consumes it);
|
|
148
|
+
* the `_counter` conflict already covers that pair.
|
|
149
|
+
* - `getNonce`: submitted as a tx it pins `_counter` and conflicts with a
|
|
150
|
+
* same-block mint or burn.
|
|
151
|
+
* - Views: metadata, `tokenColor` and the signer views read only state
|
|
152
|
+
* written by `initialize`, so they commute with everything.
|
|
153
|
+
*
|
|
154
|
+
* @notice Scope:
|
|
155
|
+
* Mint and burn only. No send, transfer or execute circuit exists, so
|
|
156
|
+
* contract-held coins leave only through `burn`. No deposit or receive
|
|
157
|
+
* circuit exists either: `burn` spends a coin the contract already holds,
|
|
158
|
+
* supplied by the operator as `QualifiedShieldedCoinInfo`. Coins minted to
|
|
159
|
+
* users are ordinary Zswap coins the contract neither tracks nor restricts.
|
|
160
|
+
* No supply accounting.
|
|
161
|
+
*
|
|
162
|
+
* @notice Client-side duties:
|
|
163
|
+
* - Before deploy: pick a random `instanceSalt`, derive each `cm` with
|
|
164
|
+
* `_calculateSignerId(pk, salt)`, pass the three as `signerCommitments`.
|
|
165
|
+
* - Per operation: read `getNonce()`, rebuild the digest as
|
|
166
|
+
* `EcdsaTestUtils.ts` does, collect two low-s signatures off-chain, submit
|
|
167
|
+
* keys and signatures in matching `Vector<2>` positions.
|
|
168
|
+
* - Coin delivery: hand the `ShieldedCoinInfo` returned by `mint` to the
|
|
169
|
+
* recipient out of band; contract-initiated mints create no ciphertext, so
|
|
170
|
+
* a wallet cannot find the coin by scanning.
|
|
171
|
+
* - Coin tracking: keep contract-held coins off-chain, recovering each
|
|
172
|
+
* coin's `mt_index` from the indexer's `ZswapOutput` / ledger events to
|
|
173
|
+
* build the `QualifiedShieldedCoinInfo` that `burn` takes.
|
|
174
|
+
* - Change coin: persist the one `burn` returns. It replaces the spent coin
|
|
175
|
+
* as the contract's holding and is not otherwise recoverable.
|
|
176
|
+
*
|
|
177
|
+
* @notice Scalability:
|
|
178
|
+
* - Throughput: one mint-or-burn per block (see Concurrency).
|
|
179
|
+
* - State: constant-size, two counters and a three-entry signer set. Nothing
|
|
180
|
+
* grows per operation.
|
|
181
|
+
* - Amounts: `mint` caps at `Uint<64>` by `mintShieldedToken`; `burn` takes
|
|
182
|
+
* `Uint<64>` by its signature although `NST__burnFromSelf` accepts
|
|
183
|
+
* `Uint<128>`.
|
|
184
|
+
*
|
|
185
|
+
* @dev Composition:
|
|
186
|
+
* - Recipient privacy: pass a caller-supplied secret random nonce to
|
|
187
|
+
* `NST__mint` instead of `NSTDerivedNonce__deriveNonce()`.
|
|
188
|
+
* - Deposits: a deposit path needs a `receiveShielded` circuit; none is
|
|
189
|
+
* included.
|
|
190
|
+
*
|
|
191
|
+
* @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
|
|
192
|
+
*/
|
|
193
|
+
module NativeShieldedTokenIssuer {
|
|
194
|
+
import CompactStandardLibrary;
|
|
195
|
+
|
|
196
|
+
import "../EcdsaSignerManager" prefix EcdsaSignerManager_;
|
|
197
|
+
import "../../crypto/Eip712" prefix Eip712_;
|
|
198
|
+
import "../../utils/Utils" prefix Utils_;
|
|
199
|
+
import "../../utils/EvmAbi" prefix EvmAbi_;
|
|
200
|
+
import "../../token/NativeShieldedToken" prefix NST_;
|
|
201
|
+
import "../../token/extensions/NativeShieldedTokenDerivedNonce" prefix NSTDerivedNonce_;
|
|
202
|
+
|
|
203
|
+
// Named import/re-export of the composed modules' state only, so an
|
|
204
|
+
// implementing contract can surface every ledger field under its own key.
|
|
205
|
+
// The circuits stay behind the prefixes above. The derived-nonce counter is
|
|
206
|
+
// aliased because this module's replay counter already owns `_counter`.
|
|
207
|
+
import { _instanceSalt, _signers, _signerCount, _threshold } from "../EcdsaSignerManager";
|
|
208
|
+
import { _domain, _name, _symbol, _decimals, _isInitialized } from "../../token/NativeShieldedToken";
|
|
209
|
+
import { _counter as _derivedNonceCounter } from "../../token/extensions/NativeShieldedTokenDerivedNonce";
|
|
210
|
+
export { _instanceSalt, _signers, _signerCount, _threshold };
|
|
211
|
+
export { _domain, _name, _symbol, _decimals, _isInitialized };
|
|
212
|
+
export { _derivedNonceCounter };
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @description Signature replay counter. `mint` and `burn` read it into
|
|
216
|
+
* their digest and increment it; `getNonce` returns the value the next
|
|
217
|
+
* digest binds to. The read is pinned, so at most one mint-or-burn lands
|
|
218
|
+
* per block.
|
|
219
|
+
*/
|
|
220
|
+
export ledger _counter: Counter;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @description The EIP-712 domain separator every operation digest is
|
|
224
|
+
* built under, derived from `(name, version, instanceSalt)`. Set once at
|
|
225
|
+
* construction.
|
|
226
|
+
*/
|
|
227
|
+
export sealed ledger _domainSeparator: Bytes<32>;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @description Registers the signer set, initializes the token metadata
|
|
231
|
+
* and domain, and fixes the EIP-712 domain separator. The threshold is
|
|
232
|
+
* fixed at 2.
|
|
233
|
+
*
|
|
234
|
+
* @notice Call from the implementing contract's constructor only. It writes
|
|
235
|
+
* the token module's `sealed` domain and `_domainSeparator`, so
|
|
236
|
+
* implementing contracts MUST NOT expose it as a post-deploy circuit.
|
|
237
|
+
*
|
|
238
|
+
* `instanceSalt` is both the EIP-712 domain salt and the signer-commitment
|
|
239
|
+
* salt, so a fresh value per deployment keeps one public key from
|
|
240
|
+
* producing the same commitment in two deployments.
|
|
241
|
+
*
|
|
242
|
+
* The token color is `tokenType(tokenDomain, kernel.self())`. Only coins
|
|
243
|
+
* of this color can be burned through this contract.
|
|
244
|
+
*
|
|
245
|
+
* Requirements:
|
|
246
|
+
*
|
|
247
|
+
* - `instanceSalt` should be cryptographically random.
|
|
248
|
+
* - `signerCommitments` must not contain duplicates.
|
|
249
|
+
*
|
|
250
|
+
* @param {Bytes<32>} instanceSalt - Per-deployment salt for commitment derivation.
|
|
251
|
+
* @param {Bytes<32>} tokenDomain - Domain separator for this contract's token color.
|
|
252
|
+
* @param {Opaque<"string">} name_ - The token name.
|
|
253
|
+
* @param {Opaque<"string">} symbol_ - The token symbol.
|
|
254
|
+
* @param {Uint<8>} decimals_ - The token decimals.
|
|
255
|
+
* @param {Vector<3, Bytes<32>>} signerCommitments - Hashed signer identities.
|
|
256
|
+
* @returns {[]} Empty tuple.
|
|
257
|
+
*/
|
|
258
|
+
export circuit initialize(
|
|
259
|
+
instanceSalt: Bytes<32>,
|
|
260
|
+
tokenDomain: Bytes<32>,
|
|
261
|
+
name_: Opaque<"string">,
|
|
262
|
+
symbol_: Opaque<"string">,
|
|
263
|
+
decimals_: Uint<8>,
|
|
264
|
+
signerCommitments: Vector<3, Bytes<32>>,
|
|
265
|
+
): [] {
|
|
266
|
+
EcdsaSignerManager_initialize<3>(instanceSalt, signerCommitments, 2);
|
|
267
|
+
NST_initialize(tokenDomain, name_, symbol_, decimals_);
|
|
268
|
+
|
|
269
|
+
const hashedName = keccak256<Bytes<25>>(pad(25, "NativeShieldedTokenIssuer"));
|
|
270
|
+
const hashedVersion = keccak256<Bytes<1>>(pad(1, "1"));
|
|
271
|
+
_domainSeparator = Eip712_domainSeparator(
|
|
272
|
+
hashedName,
|
|
273
|
+
hashedVersion,
|
|
274
|
+
disclose(instanceSalt)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @description `keccak256` of the `Mint` type string:
|
|
280
|
+
* `Mint(bytes32 contractAddress,bytes32 recipient,bool isContract,uint256 nonce,uint256 amount)`
|
|
281
|
+
*
|
|
282
|
+
* @dev Written as a literal word; see `BURN_TYPEHASH`. The spec checks this
|
|
283
|
+
* word against the type string above.
|
|
284
|
+
*
|
|
285
|
+
* @returns {Bytes<32>} The `Mint` type hash.
|
|
286
|
+
*/
|
|
287
|
+
pure circuit MINT_TYPEHASH(): Bytes<32> {
|
|
288
|
+
const typeHash: Vector<32, Uint<8>> = [
|
|
289
|
+
0x28, 0xd4, 0xc8, 0x40, 0xbc, 0x95, 0xfe, 0x08,
|
|
290
|
+
0x4e, 0xa5, 0xe3, 0x20, 0x94, 0x43, 0xba, 0x67,
|
|
291
|
+
0x21, 0xb7, 0x17, 0x09, 0x71, 0xee, 0x05, 0xef,
|
|
292
|
+
0xaf, 0x0a, 0x98, 0x51, 0xee, 0x5f, 0xd1, 0xf5
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
return typeHash as Bytes<32>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @description `keccak256` of the `Burn` type string:
|
|
300
|
+
* `Burn(bytes32 contractAddress,uint256 nonce,uint256 amount)`
|
|
301
|
+
*
|
|
302
|
+
* @dev Written as a literal word. `compactc` emits a full keccak
|
|
303
|
+
* permutation even for a constant argument, so hashing the type string
|
|
304
|
+
* in-circuit would add ~4.2k rows to every operation. The spec checks this
|
|
305
|
+
* word against the type string above.
|
|
306
|
+
*
|
|
307
|
+
* @returns {Bytes<32>} The `Burn` type hash.
|
|
308
|
+
*/
|
|
309
|
+
pure circuit BURN_TYPEHASH(): Bytes<32> {
|
|
310
|
+
const typeHash: Vector<32, Uint<8>> = [
|
|
311
|
+
0xdf, 0x62, 0xd6, 0x7d, 0x39, 0x08, 0x06, 0xa6,
|
|
312
|
+
0xb6, 0x3f, 0xbd, 0x9f, 0xb5, 0xb8, 0x90, 0x4e,
|
|
313
|
+
0xf8, 0x6a, 0x4d, 0x67, 0xf3, 0x82, 0x4a, 0x3d,
|
|
314
|
+
0x85, 0x5f, 0x6e, 0xef, 0xb8, 0xe7, 0x98, 0x09
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
return typeHash as Bytes<32>;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @description Computes a signer commitment from a secp256k1 public key,
|
|
322
|
+
* as `persistentHash(pkX, pkY, salt, "multisig:signer:")`.
|
|
323
|
+
*
|
|
324
|
+
* Pure, so the deployer can derive constructor commitments off-chain.
|
|
325
|
+
*
|
|
326
|
+
* Requirements:
|
|
327
|
+
*
|
|
328
|
+
* - `pk` must not be the secp256k1 identity point, which has no coordinates.
|
|
329
|
+
*
|
|
330
|
+
* @param {Secp256k1Point} pk - The secp256k1 public key.
|
|
331
|
+
* @param {Bytes<32>} salt - The instance salt.
|
|
332
|
+
* @returns {Bytes<32>} The signer commitment.
|
|
333
|
+
*/
|
|
334
|
+
export pure circuit _calculateSignerId(
|
|
335
|
+
pk: Secp256k1Point,
|
|
336
|
+
salt: Bytes<32>
|
|
337
|
+
): Bytes<32> {
|
|
338
|
+
return EcdsaSignerManager_calculateSignerId(pk, salt);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @description Mints a new shielded coin of this contract's token to
|
|
343
|
+
* `recipient`, authorized by threshold signatures.
|
|
344
|
+
*
|
|
345
|
+
* The coin nonce comes from `NativeShieldedTokenDerivedNonce`, so anyone
|
|
346
|
+
* can test a candidate recipient against the coin commitment.
|
|
347
|
+
*
|
|
348
|
+
* @notice The returned coin info is the only copy available to the
|
|
349
|
+
* recipient; deliver it out of band.
|
|
350
|
+
*
|
|
351
|
+
* @constraints k=17, rows=89952 (zkir-v3)
|
|
352
|
+
*
|
|
353
|
+
* Requirements:
|
|
354
|
+
*
|
|
355
|
+
* - Both public keys must hash to registered signer commitments.
|
|
356
|
+
* - Both signatures must be valid over the mint message hash.
|
|
357
|
+
* - Signers must not be duplicates.
|
|
358
|
+
* - Threshold must be met.
|
|
359
|
+
* - `recipient` is not zero.
|
|
360
|
+
*
|
|
361
|
+
* @param {Uint<64>} amount - The token amount to mint.
|
|
362
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The address to receive the minted tokens.
|
|
363
|
+
* @param {Vector<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
364
|
+
* @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the mint hash.
|
|
365
|
+
* @returns {ShieldedCoinInfo} The minted coin (nonce, color, value).
|
|
366
|
+
*/
|
|
367
|
+
export circuit mint(
|
|
368
|
+
amount: Uint<64>,
|
|
369
|
+
recipient: Either<ZswapCoinPublicKey, ContractAddress>,
|
|
370
|
+
pubkeys: Vector<2, Secp256k1Point>,
|
|
371
|
+
signatures: Vector<2, Secp256k1EcdsaSignature>
|
|
372
|
+
): ShieldedCoinInfo {
|
|
373
|
+
const opNonce = _counter;
|
|
374
|
+
_counter.increment(1);
|
|
375
|
+
|
|
376
|
+
// Canonicalize recipient to ensure garbage values don't sully the hash
|
|
377
|
+
const canonRecipient = Utils_canonicalize<ZswapCoinPublicKey, ContractAddress>(recipient);
|
|
378
|
+
|
|
379
|
+
// Only the active arm reaches the digest, so a crafted value carrying data
|
|
380
|
+
// in its unused arm cannot alter what was signed.
|
|
381
|
+
const recipientWord = canonRecipient.is_left
|
|
382
|
+
? canonRecipient.left.bytes
|
|
383
|
+
: canonRecipient.right.bytes;
|
|
384
|
+
|
|
385
|
+
const msgHash = Eip712_hashTypedData(_domainSeparator, keccak256<Vector<6, Bytes<32>>>([
|
|
386
|
+
MINT_TYPEHASH(),
|
|
387
|
+
// Binds the digest to this deployment. The domain cannot carry the
|
|
388
|
+
// address, so removing this word removes cross-instance replay
|
|
389
|
+
// protection.
|
|
390
|
+
kernel.self().bytes,
|
|
391
|
+
recipientWord,
|
|
392
|
+
EvmAbi_boolWord(!canonRecipient.is_left),
|
|
393
|
+
EvmAbi_uint64Word(opNonce),
|
|
394
|
+
EvmAbi_uint64Word(amount)
|
|
395
|
+
]));
|
|
396
|
+
|
|
397
|
+
EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
|
|
398
|
+
|
|
399
|
+
return NST__mint(canonRecipient, amount, NSTDerivedNonce__deriveNonce());
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @description Burns `amount` from a coin this contract holds, authorized
|
|
404
|
+
* by threshold signatures.
|
|
405
|
+
*
|
|
406
|
+
* The operator supplies the `QualifiedShieldedCoinInfo` from the off-chain
|
|
407
|
+
* UTXO pool. On a partial burn the change is auto-received by the contract
|
|
408
|
+
* and returned; it replaces `coin` as the contract's holding, so the
|
|
409
|
+
* operator must persist it.
|
|
410
|
+
*
|
|
411
|
+
* @constraints k=17, rows=97232 (zkir-v3)
|
|
412
|
+
*
|
|
413
|
+
* Requirements:
|
|
414
|
+
*
|
|
415
|
+
* - Both public keys must hash to registered signer commitments.
|
|
416
|
+
* - Both signatures must be valid over the burn message hash.
|
|
417
|
+
* - Signers must not be duplicates.
|
|
418
|
+
* - Threshold must be met.
|
|
419
|
+
* - `coin.color` is this contract's token color.
|
|
420
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
421
|
+
*
|
|
422
|
+
* @param {QualifiedShieldedCoinInfo} coin - The contract-held coin to burn from.
|
|
423
|
+
* @param {Uint<64>} amount - The token amount to burn.
|
|
424
|
+
* @param {Vector<2, Secp256k1Point>} pubkeys - Public keys of approving signers.
|
|
425
|
+
* @param {Vector<2, Secp256k1EcdsaSignature>} signatures - ECDSA signatures over the burn hash.
|
|
426
|
+
* @returns {Maybe<ShieldedCoinInfo>} The change coin retained by the
|
|
427
|
+
* contract, or `none` if the coin was burned in full.
|
|
428
|
+
*/
|
|
429
|
+
export circuit burn(
|
|
430
|
+
coin: QualifiedShieldedCoinInfo,
|
|
431
|
+
amount: Uint<64>,
|
|
432
|
+
pubkeys: Vector<2, Secp256k1Point>,
|
|
433
|
+
signatures: Vector<2, Secp256k1EcdsaSignature>
|
|
434
|
+
): Maybe<ShieldedCoinInfo> {
|
|
435
|
+
const opNonce = _counter;
|
|
436
|
+
_counter.increment(1);
|
|
437
|
+
|
|
438
|
+
const msgHash = Eip712_hashTypedData(_domainSeparator, keccak256<Vector<4, Bytes<32>>>([
|
|
439
|
+
BURN_TYPEHASH(),
|
|
440
|
+
// Binds the digest to this deployment. The domain cannot carry the
|
|
441
|
+
// address, so removing this word removes cross-instance replay
|
|
442
|
+
// protection.
|
|
443
|
+
kernel.self().bytes,
|
|
444
|
+
EvmAbi_uint64Word(opNonce),
|
|
445
|
+
EvmAbi_uint64Word(amount)
|
|
446
|
+
]));
|
|
447
|
+
|
|
448
|
+
EcdsaSignerManager_assertApprovals(msgHash, pubkeys, signatures);
|
|
449
|
+
|
|
450
|
+
return NST__burnFromSelf(coin, amount);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @description Returns the counter the next mint or burn digest will bind to.
|
|
455
|
+
*
|
|
456
|
+
* @constraints k=6, rows=34 (zkir-v3)
|
|
457
|
+
*
|
|
458
|
+
* @returns {Uint<64>} The current counter.
|
|
459
|
+
*/
|
|
460
|
+
export circuit getNonce(): Uint<64> {
|
|
461
|
+
return _counter;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @description Returns the token name.
|
|
466
|
+
*
|
|
467
|
+
* @constraints k=6, rows=45 (zkir-v3)
|
|
468
|
+
*
|
|
469
|
+
* @returns {Opaque<"string">} The token name.
|
|
470
|
+
*/
|
|
471
|
+
export circuit name(): Opaque<"string"> {
|
|
472
|
+
return NST_name();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* @description Returns the token symbol.
|
|
477
|
+
*
|
|
478
|
+
* @constraints k=6, rows=45 (zkir-v3)
|
|
479
|
+
*
|
|
480
|
+
* @returns {Opaque<"string">} The token symbol.
|
|
481
|
+
*/
|
|
482
|
+
export circuit symbol(): Opaque<"string"> {
|
|
483
|
+
return NST_symbol();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* @description Returns the token decimals.
|
|
488
|
+
*
|
|
489
|
+
* @constraints k=6, rows=45 (zkir-v3)
|
|
490
|
+
*
|
|
491
|
+
* @returns {Uint<8>} The decimals value.
|
|
492
|
+
*/
|
|
493
|
+
export circuit decimals(): Uint<8> {
|
|
494
|
+
return NST_decimals();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @description Returns this contract's token color,
|
|
499
|
+
* `tokenType(tokenDomain, kernel.self())`.
|
|
500
|
+
*
|
|
501
|
+
* @constraints k=13, rows=3847 (zkir-v3)
|
|
502
|
+
*
|
|
503
|
+
* @returns {Bytes<32>} The coin color.
|
|
504
|
+
*/
|
|
505
|
+
export circuit tokenColor(): Bytes<32> {
|
|
506
|
+
return NST_tokenColor();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* @description Returns the number of registered signers.
|
|
511
|
+
*
|
|
512
|
+
* @constraints k=6, rows=34 (zkir-v3)
|
|
513
|
+
*
|
|
514
|
+
* @returns {Uint<8>} The signer count.
|
|
515
|
+
*/
|
|
516
|
+
export circuit getSignerCount(): Uint<8> {
|
|
517
|
+
return EcdsaSignerManager_getSignerCount();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @description Returns the approval threshold.
|
|
522
|
+
*
|
|
523
|
+
* @constraints k=6, rows=34 (zkir-v3)
|
|
524
|
+
*
|
|
525
|
+
* @returns {Uint<8>} The threshold.
|
|
526
|
+
*/
|
|
527
|
+
export circuit getThreshold(): Uint<8> {
|
|
528
|
+
return EcdsaSignerManager_getThreshold();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* @description Returns whether the given commitment is a registered signer.
|
|
533
|
+
*
|
|
534
|
+
* @constraints k=8, rows=129 (zkir-v3)
|
|
535
|
+
*
|
|
536
|
+
* @param {Bytes<32>} commitment - The signer commitment to check.
|
|
537
|
+
* @returns {Boolean} True if registered.
|
|
538
|
+
*/
|
|
539
|
+
export circuit isSigner(commitment: Bytes<32>): Boolean {
|
|
540
|
+
return EcdsaSignerManager_isSigner(commitment);
|
|
541
|
+
}
|
|
542
|
+
}
|