@openzeppelin/compact-contracts 0.4.0-alpha.1 → 0.4.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +9 -5
  2. package/access/AccessControl.compact +13 -13
  3. package/access/Ownable.compact +15 -9
  4. package/access/ShieldedAccessControl.compact +25 -1
  5. package/access/ZOwnablePK.compact +8 -8
  6. package/crypto/EcdhMask.compact +1 -1
  7. package/crypto/Ecdsa.compact +4 -4
  8. package/crypto/Eip712.compact +168 -0
  9. package/crypto/ElGamal.compact +44 -3
  10. package/multisig/EcdsaSignerManager.compact +32 -15
  11. package/multisig/ForwarderPrivate.compact +11 -5
  12. package/multisig/ForwarderShielded.compact +2 -2
  13. package/multisig/ForwarderUnshielded.compact +2 -2
  14. package/multisig/ProposalManager.compact +194 -22
  15. package/multisig/ShieldedTreasury.compact +28 -1
  16. package/multisig/ShieldedTreasuryStateless.compact +1 -1
  17. package/multisig/Signer.compact +27 -79
  18. package/multisig/UnshieldedTreasury.compact +9 -1
  19. package/multisig/{presets/forwarder/ForwarderPrivate.compact → examples/ForwarderPrivateExample.compact} +3 -3
  20. package/multisig/{presets/forwarder/ForwarderShielded.compact → examples/ForwarderShieldedExample.compact} +3 -3
  21. package/multisig/{presets/forwarder/ForwarderUnshielded.compact → examples/ForwarderUnshieldedExample.compact} +3 -3
  22. package/multisig/examples/NativeShieldedTokenIssuerExample.compact +143 -0
  23. package/multisig/examples/ShieldedMultiSigV2Example.compact +77 -0
  24. package/multisig/presets/NativeShieldedTokenIssuer.compact +557 -0
  25. package/multisig/presets/ShieldedMultiSigV2.compact +251 -188
  26. package/package.json +15 -11
  27. package/security/Allowlist.compact +5 -5
  28. package/security/Blocklist.compact +5 -5
  29. package/security/Initializable.compact +8 -4
  30. package/security/Pausable.compact +10 -6
  31. package/token/ConfidentialFungibleToken.compact +337 -79
  32. package/token/FungibleToken.compact +35 -21
  33. package/token/MultiToken.compact +15 -15
  34. package/token/NativeShieldedToken.compact +65 -16
  35. package/token/NativeShieldedTokenCore.compact +57 -15
  36. package/token/NativeShieldedTokenFamily.compact +66 -16
  37. package/token/NonFungibleToken.compact +83 -34
  38. package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +11 -6
  39. package/token/extensions/NativeShieldedTokenDerivedNonce.compact +2 -2
  40. package/token/extensions/NativeShieldedTokenFamilyPublicSupply.compact +7 -7
  41. package/token/extensions/NativeShieldedTokenPublicSupply.compact +7 -7
  42. package/token/extensions/NativeShieldedTokenPublicSupplyCore.compact +5 -5
  43. package/utils/EvmAbi.compact +134 -0
  44. package/utils/Utils.compact +11 -1
  45. package/multisig/presets/ShieldedMultiSig.compact +0 -227
  46. package/multisig/presets/ShieldedMultiSigV3.compact +0 -311
package/README.md CHANGED
@@ -189,11 +189,15 @@ Compiling 14 circuits:
189
189
  Overall progress [====================] 14/14
190
190
  ```
191
191
 
192
- ZKIR v3: the library targets ZKIR v2, the compiler 0.34.0 default. Pass
193
- `--feature-zkir-v3` only when the contract imports the secp256k1-based modules
194
- (`crypto/Ecdsa`, `multisig/EcdsaSignerManager`, `ShieldedMultiSigV2`,
195
- `ShieldedMultiSigV3`). Compiling the other modules under v3 currently hits
196
- upstream blockers, listed under [Known issues](CHANGELOG.md#known-issues).
192
+ ZKIR v3: the library targets ZKIR v2, the compiler 0.34.0 default.
193
+ Pass `--feature-zkir-v3` only when the contract reaches a module that needs it:
194
+ the secp256k1-based `crypto/Ecdsa` and `multisig/EcdsaSignerManager`,
195
+ or the `keccak256`-based `crypto/Eip712`.
196
+ `multisig/presets/ShieldedMultiSigV2`, `multisig/presets/NativeShieldedTokenIssuer`
197
+ and the `multisig/examples/` contracts reach both.
198
+ `utils/EvmAbi` needs no `keccak256` of its own and compiles on either backend.
199
+ Compiling the other modules under v3 currently hits upstream blockers,
200
+ listed under [Known issues](CHANGELOG.md#known-issues).
197
201
 
198
202
  ## Development
199
203
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (access/AccessControl.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (access/AccessControl.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -168,7 +168,7 @@ module AccessControl {
168
168
  /**
169
169
  * @description Returns `true` if `account` has been granted `roleId`.
170
170
  *
171
- * @circuitInfo k=10, rows=1019
171
+ * @constraints k=10, rows=1017
172
172
  *
173
173
  * @param {Bytes<32>} roleId - The role identifier.
174
174
  * @param {Either<Bytes<32>, ContractAddress>} account - The account to query.
@@ -206,7 +206,7 @@ module AccessControl {
206
206
  * In the case of an external (non-contract) caller, the caller’s identity is
207
207
  * derived from the `wit_AccessControlSK` witness as `persistentHash(secretKey)`.
208
208
  *
209
- * @circuitInfo k=13, rows=2669
209
+ * @constraints k=13, rows=2660
210
210
  *
211
211
  * Requirements:
212
212
  *
@@ -225,7 +225,7 @@ module AccessControl {
225
225
  /**
226
226
  * @description Reverts if `account` is missing `roleId`.
227
227
  *
228
- * @circuitInfo k=10, rows=987
228
+ * @constraints k=10, rows=997
229
229
  *
230
230
  * Requirements:
231
231
  *
@@ -248,7 +248,7 @@ module AccessControl {
248
248
  *
249
249
  * To change a role's admin use {_setRoleAdmin}.
250
250
  *
251
- * @circuitInfo k=9, rows=373
251
+ * @constraints k=9, rows=371
252
252
  *
253
253
  * @param {Bytes<32>} roleId - The role identifier.
254
254
  * @return {Bytes<32>} roleAdmin - The admin role that controls `roleId`.
@@ -263,7 +263,7 @@ module AccessControl {
263
263
  /**
264
264
  * @description Grants `roleId` to `account`.
265
265
  *
266
- * @circuitInfo k=13, rows=3536
266
+ * @constraints k=13, rows=3527
267
267
  *
268
268
  * Requirements:
269
269
  *
@@ -285,7 +285,7 @@ module AccessControl {
285
285
  /**
286
286
  * @description Revokes `roleId` from `account`.
287
287
  *
288
- * @circuitInfo k=13, rows=3466
288
+ * @constraints k=13, rows=3457
289
289
  *
290
290
  * Requirements:
291
291
  *
@@ -314,7 +314,7 @@ module AccessControl {
314
314
  * `persistentHash(secretKey)`. The `callerConfirmation` parameter must match
315
315
  * the caller's computed account identifier to prevent accidental renunciation.
316
316
  *
317
- * @circuitInfo k=13, rows=3322
317
+ * @constraints k=13, rows=3313
318
318
  *
319
319
  * Requirements:
320
320
  *
@@ -339,7 +339,7 @@ module AccessControl {
339
339
  /**
340
340
  * @description Sets `adminRole` as `roleId`'s admin role.
341
341
  *
342
- * @circuitInfo k=10, rows=581
342
+ * @constraints k=10, rows=579
343
343
  *
344
344
  * @param {Bytes<32>} roleId - The role identifier.
345
345
  * @param {Bytes<32>} adminRole - The admin role identifier.
@@ -353,7 +353,7 @@ module AccessControl {
353
353
  * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
354
354
  * Internal circuit without access restriction.
355
355
  *
356
- * @circuitInfo k=11, rows=1143
356
+ * @constraints k=11, rows=1141
357
357
  *
358
358
  * Requirements:
359
359
  *
@@ -376,7 +376,7 @@ module AccessControl {
376
376
  * @description Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
377
377
  * Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress.
378
378
  *
379
- * @circuitInfo k=11, rows=1142
379
+ * @constraints k=11, rows=1140
380
380
  *
381
381
  * @notice External smart contracts cannot call the access control contract at this time, so granting a role to a ContractAddress may
382
382
  * render a circuit permanently inaccessible.
@@ -409,7 +409,7 @@ module AccessControl {
409
409
  * @description Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked.
410
410
  * Internal circuit without access restriction.
411
411
  *
412
- * @circuitInfo k=11, rows=1073
412
+ * @constraints k=11, rows=1071
413
413
  *
414
414
  * @param {Bytes<32>} roleId - The role identifier.
415
415
  * @param {Either<Bytes<32>, ContractAddress>} account - A Bytes<32> or ContractAddress.
@@ -437,7 +437,7 @@ module AccessControl {
437
437
  *
438
438
  * The result is a 32-byte commitment that uniquely identifies the caller.
439
439
  *
440
- * @circuitInfo k=13, rows=2294
440
+ * @constraints k=13, rows=2294
441
441
  *
442
442
  * @returns {Bytes<32>} accountId - The computed account identifier.
443
443
  */
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (access/Ownable.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (access/Ownable.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -94,6 +94,12 @@ module Ownable {
94
94
  */
95
95
  export ledger _isInitialized: Boolean;
96
96
 
97
+ /**
98
+ * @description The current owner, canonicalized on write. A `left` holds an
99
+ * account identifier, stored as supplied and never checked against the secret
100
+ * key it should derive from; a `right` holds a contract address. The zero
101
+ * value means ownership has been renounced.
102
+ */
97
103
  export ledger _owner: Either<Bytes<32>, ContractAddress>;
98
104
 
99
105
  /**
@@ -111,7 +117,7 @@ module Ownable {
111
117
  * @description Initializes the contract by setting the `initialOwner`.
112
118
  * This must be called in the contract's constructor.
113
119
  *
114
- * @circuitInfo k=10, rows=626
120
+ * @constraints k=10, rows=626
115
121
  *
116
122
  * Requirements:
117
123
  *
@@ -158,7 +164,7 @@ module Ownable {
158
164
  /**
159
165
  * @description Returns the current contract owner.
160
166
  *
161
- * @circuitInfo k=7, rows=76
167
+ * @constraints k=7, rows=74
162
168
  *
163
169
  * Requirements:
164
170
  *
@@ -178,7 +184,7 @@ module Ownable {
178
184
  * interactions are supported in Compact.
179
185
  * This restriction prevents permanently disabling access to a circuit.
180
186
  *
181
- * @circuitInfo k=13, rows=2959
187
+ * @constraints k=13, rows=2947
182
188
  *
183
189
  * Requirements:
184
190
  *
@@ -205,7 +211,7 @@ module Ownable {
205
211
  * Ownership privileges sent to a contract address may become uncallable.
206
212
  * Once contract-to-contract calls are supported, this circuit may be deprecated.
207
213
  *
208
- * @circuitInfo k=13, rows=2956
214
+ * @constraints k=13, rows=2944
209
215
  *
210
216
  * Requirements:
211
217
  *
@@ -228,7 +234,7 @@ module Ownable {
228
234
  * It will not be possible to call `assertOnlyOwner` circuits anymore.
229
235
  * Can only be called by the current owner.
230
236
  *
231
- * @circuitInfo k=13, rows=2364
237
+ * @constraints k=13, rows=2353
232
238
  *
233
239
  * Requirements:
234
240
  *
@@ -256,7 +262,7 @@ module Ownable {
256
262
  * In the case of an external (non-contract) caller, the caller’s identity is
257
263
  * derived from the `wit_OwnableSK` witness as `persistentHash(secretKey)`.
258
264
  *
259
- * @circuitInfo k=13, rows=2360
265
+ * @constraints k=13, rows=2349
260
266
  *
261
267
  * Requirements:
262
268
  *
@@ -282,7 +288,7 @@ module Ownable {
282
288
  * interactions are supported in Compact.
283
289
  * This restriction prevents circuits from being inadvertently locked in contracts.
284
290
  *
285
- * @circuitInfo k=10, rows=600
291
+ * @constraints k=10, rows=598
286
292
  *
287
293
  * Requirements:
288
294
  *
@@ -307,7 +313,7 @@ module Ownable {
307
313
  * Ownership privileges sent to a contract address may become uncallable.
308
314
  * Once contract-to-contract calls are supported, this circuit may be deprecated.
309
315
  *
310
- * @circuitInfo k=10, rows=597
316
+ * @constraints k=10, rows=595
311
317
  *
312
318
  * Requirements:
313
319
  *
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (access/ShieldedAccessControl.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (access/ShieldedAccessControl.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -199,6 +199,10 @@ module ShieldedAccessControl {
199
199
  /**
200
200
  * @ledger _adminRoles
201
201
  * @description Mapping from a role identifier to an admin role identifier.
202
+ * @type {RoleIdentifier} role - The role being administered.
203
+ * @type {RoleIdentifier} adminRole - The role permitted to grant and revoke it.
204
+ * @type {Map<role, adminRole>}
205
+ * @type {Map<RoleIdentifier, RoleIdentifier>} _adminRoles
202
206
  */
203
207
  export ledger _adminRoles: Map<RoleIdentifier, RoleIdentifier>;
204
208
 
@@ -311,6 +315,8 @@ module ShieldedAccessControl {
311
315
  /**
312
316
  * @description Reverts if caller cannot provide a valid proof of ownership for `role`.
313
317
  *
318
+ * @constraints k=15, rows=16997
319
+ *
314
320
  * Requirements:
315
321
  *
316
322
  * - caller must prove ownership of `role`.
@@ -337,6 +343,8 @@ module ShieldedAccessControl {
337
343
  * caller if the proving environment supplies an invalid Merkle path. This circuit will never return true for an
338
344
  * unauthorized caller.
339
345
  *
346
+ * @constraints k=15, rows=16997
347
+ *
340
348
  * Requirements:
341
349
  *
342
350
  * - caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
@@ -392,6 +400,8 @@ module ShieldedAccessControl {
392
400
  * limited Merkle tree storage slots. Once revoked, a role cannot be re-granted to the same `accountId`. A new
393
401
  * `accountId` must be generated to be re-authorized for a revoked `role`.
394
402
  *
403
+ * @constraints k=15, rows=28995
404
+ *
395
405
  * Requirements:
396
406
  *
397
407
  * - caller must prove they're an admin for `role`.
@@ -434,6 +444,8 @@ module ShieldedAccessControl {
434
444
  * but are otherwise benign. A single nullifier invalidates all duplicate commitments.
435
445
  * Implementing contracts are responsible for mitigating tree exhaustion risk.
436
446
  *
447
+ * @constraints k=14, rows=12250
448
+ *
437
449
  * Requirements:
438
450
  *
439
451
  * - The `(role, accountId)` pairing must not be already revoked.
@@ -466,6 +478,8 @@ module ShieldedAccessControl {
466
478
  * @warning Outside observers may be able to use timing and pattern analysis to weaken pseudonymity
467
479
  * guarantees if renounceRole is used in tandem with other on-chain actions.
468
480
  *
481
+ * @constraints k=14, rows=14440
482
+ *
469
483
  * Requirements:
470
484
  *
471
485
  * - The caller must provide a valid `accountId` for the role.
@@ -497,6 +511,8 @@ module ShieldedAccessControl {
497
511
  * `role`. At this time, proofs of non-membership on values in the Merkle tree LDT are not possible
498
512
  * so a `(role, accountId)` pairing that does not exist can still be revoked.
499
513
  *
514
+ * @constraints k=15, rows=27005
515
+ *
500
516
  * Requirements:
501
517
  *
502
518
  * - caller must prove they're an admin for `role`.
@@ -533,6 +549,8 @@ module ShieldedAccessControl {
533
549
  * @warning Exposing this circuit directly in an implementing contract would allow anyone to revoke
534
550
  * roles without authorization. It must be wrapped with appropriate access control.
535
551
  *
552
+ * @constraints k=14, rows=10260
553
+ *
536
554
  * Requirements:
537
555
  *
538
556
  * - The `(role, accountId)` pairing must not be already revoked.
@@ -599,6 +617,8 @@ module ShieldedAccessControl {
599
617
  *
600
618
  * To change a role's admin use {_setRoleAdmin}.
601
619
  *
620
+ * @constraints k=9, rows=374
621
+ *
602
622
  * Disclosures:
603
623
  *
604
624
  * - A role identifier.
@@ -621,6 +641,8 @@ module ShieldedAccessControl {
621
641
  * @warning Exposing this circuit directly in an implementing contract would allow anyone to assign
622
642
  * arbitrary admin roles without authorization. It must be wrapped with appropriate access control.
623
643
  *
644
+ * @constraints k=10, rows=581
645
+ *
624
646
  * Disclosures:
625
647
  *
626
648
  * - The role identifier
@@ -707,6 +729,8 @@ module ShieldedAccessControl {
707
729
  * - `commitmentDomain`: Domain separator `"ShieldedAccessControl:commitment"` (padded to 32 bytes) to prevent
708
730
  * hash collisions when extending the module or using similar commitment schemes.
709
731
  *
732
+ * @constraints k=13, rows=6404
733
+ *
710
734
  * @param {RoleIdentifier} role - The role identifier.
711
735
  * @param {AccountIdentifier} accountId - The unique identifier of the account.
712
736
  *
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (access/ZOwnablePK.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (access/ZOwnablePK.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -99,7 +99,7 @@ module ZOwnablePK {
99
99
  * @warning The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm.
100
100
  * Using any other algorithm will result in a permanent loss of contract access.
101
101
  *
102
- * @circuitInfo k=14, rows=14933
102
+ * @constraints k=14, rows=14933
103
103
  *
104
104
  * Requirements:
105
105
  *
@@ -150,7 +150,7 @@ module ZOwnablePK {
150
150
  * @description Returns the current commitment representing the contract owner.
151
151
  * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`.
152
152
  *
153
- * @circuitInfo k=10, rows=57
153
+ * @constraints k=6, rows=48
154
154
  *
155
155
  * Requirements:
156
156
  *
@@ -167,7 +167,7 @@ module ZOwnablePK {
167
167
  * @description Transfers ownership to `newOwnerId`.
168
168
  * `newOwnerId` must be precalculated and given to the current owner off chain.
169
169
  *
170
- * @circuitInfo k=16, rows=39240
170
+ * @constraints k=15, rows=17023
171
171
  *
172
172
  * Requirements:
173
173
  *
@@ -191,7 +191,7 @@ module ZOwnablePK {
191
191
  * It will not be possible to call `assertOnlyOwner` circuits anymore.
192
192
  * Can only be called by the current owner.
193
193
  *
194
- * @circuitInfo k=15, rows=24442
194
+ * @constraints k=14, rows=10579
195
195
  *
196
196
  * Requirements:
197
197
  *
@@ -212,7 +212,7 @@ module ZOwnablePK {
212
212
  * the stored owner commitment.
213
213
  * Use this to only allow the owner to call specific circuits.
214
214
  *
215
- * @circuitInfo k=15, rows=24437
215
+ * @constraints k=14, rows=10577
216
216
  *
217
217
  * Requirements:
218
218
  *
@@ -257,7 +257,7 @@ module ZOwnablePK {
257
257
  * - `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent
258
258
  * hash collisions when extending the module or using similar commitment schemes.
259
259
  *
260
- * @circuitInfo k=14, rows=14853
260
+ * @constraints k=13, rows=6544
261
261
  *
262
262
  * Requirements:
263
263
  *
@@ -320,7 +320,7 @@ module ZOwnablePK {
320
320
  * @description Transfers ownership to owner id `newOwnerId` without
321
321
  * enforcing permission checks on the caller.
322
322
  *
323
- * @circuitInfo k=14, rows=14823
323
+ * @constraints k=13, rows=6460
324
324
  *
325
325
  * Requirements:
326
326
  *
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (crypto/EcdhMask.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (crypto/EcdhMask.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (crypto/Ecdsa.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (crypto/Ecdsa.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -21,7 +21,7 @@ module Ecdsa {
21
21
  * @description Returns whether `s` is canonical, i.e. `s <= n / 2` where `n`
22
22
  * is the secp256k1 group order.
23
23
  *
24
- * @circuitInfo k=13, rows=5125
24
+ * @constraints k=13, rows=5125 (zkir-v3)
25
25
  *
26
26
  * @param {Secp256k1Scalar} s - The `s` component of a signature.
27
27
  * @returns {Boolean} True if `s` is in the low half.
@@ -72,7 +72,7 @@ module Ecdsa {
72
72
  /**
73
73
  * @description Rejects a signature in the high half.
74
74
  *
75
- * @circuitInfo k=13, rows=5156
75
+ * @constraints k=13, rows=5156 (zkir-v3)
76
76
  *
77
77
  * Requirements:
78
78
  *
@@ -90,7 +90,7 @@ module Ecdsa {
90
90
  * low-s encoding. Reports the result instead of asserting, so a caller can
91
91
  * fold it into a count or a threshold.
92
92
  *
93
- * @circuitInfo k=16, rows=34056
93
+ * @constraints k=16, rows=34056 (zkir-v3)
94
94
  *
95
95
  * @param {Bytes<32>} msgHash - The digest that was signed.
96
96
  * @param {Secp256k1EcdsaSignature} sig - The signature over `msgHash`.
@@ -0,0 +1,168 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (crypto/Eip712.compact)
3
+
4
+ pragma language_version >= 0.26.0;
5
+
6
+ /**
7
+ * @module Eip712
8
+ * @description The EIP-712 typed-data envelope, for verifying signatures
9
+ * produced by Ethereum signing infrastructure.
10
+ *
11
+ * A typed-data signature covers `keccak256(0x1901 || domainSeparator ||
12
+ * structHash)`. The domain separator binds the signature to one application,
13
+ * so a signature obtained for any other domain cannot be replayed here — the
14
+ * property `personal_sign` does not provide, since it signs an opaque hash
15
+ * with no application binding.
16
+ *
17
+ * Build `structHash` from EVM ABI words (see `utils/EvmAbi`), with the
18
+ * operation's type hash as the first word.
19
+ *
20
+ * @notice The domain is `(name, version, salt)`. `chainId` and
21
+ * `verifyingContract` are omitted, which EIP-712 permits — every domain field
22
+ * is optional. They are omitted because neither is expressible: a Midnight
23
+ * contract address is 32 bytes and does not fit `verifyingContract`'s
24
+ * `address` type, and no network identifier is available in-circuit.
25
+ *
26
+ * On an EVM chain `verifyingContract` is what makes a domain identify one
27
+ * deployment rather than one application. Without it, a consumer has to get
28
+ * that binding elsewhere. There are two places to put it, and they are not
29
+ * equivalent:
30
+ *
31
+ * - As a field of the operation struct, holding the contract's own address.
32
+ * This is the stronger option and the one to reach for by default: a
33
+ * Midnight address is randomised at deployment and cannot be predicted, so
34
+ * no two instances share one, and a signature for one deployment cannot
35
+ * authorise another.
36
+ * - As `salt`, a per-deployment value in the domain. This is weaker, because
37
+ * it separates domains rather than messages. It is worth adding when the
38
+ * signer gates on domains but cannot read struct fields, since that is the
39
+ * only level at which it is visible.
40
+ *
41
+ * A consumer doing neither has one domain shared by every deployment of the
42
+ * contract, and nothing distinguishing them.
43
+ *
44
+ * @notice Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
45
+ */
46
+ module Eip712 {
47
+ import CompactStandardLibrary;
48
+
49
+ /**
50
+ * @description The 66-byte typed-data preimage: the two-byte prefix, the
51
+ * domain separator, then the struct hash. `Bytes<n>` is emitted at exactly
52
+ * `n` bytes, so this hashes as the concatenation an EVM signer produces.
53
+ */
54
+ struct TypedDataPreimage {
55
+ prefixBytes: Bytes<2>,
56
+ domainSeparator: Bytes<32>,
57
+ structHash: Bytes<32>
58
+ }
59
+
60
+ /**
61
+ * @description The four words of the domain separator's preimage.
62
+ */
63
+ struct DomainFields {
64
+ typeHash: Bytes<32>,
65
+ hashedName: Bytes<32>,
66
+ hashedVersion: Bytes<32>,
67
+ salt: Bytes<32>
68
+ }
69
+
70
+ /**
71
+ * @description `0x1901`, the EIP-191 version byte `0x01` behind the `0x19`
72
+ * marker, which is how EIP-712 identifies itself.
73
+ *
74
+ * @dev Written as byte values because the bytes are not printable. The
75
+ * version byte is what keeps a typed-data digest from colliding with a
76
+ * `personal_sign` one (version `0x45`) or an intended-validator one (`0x00`).
77
+ *
78
+ * @returns {Bytes<2>} The typed-data prefix.
79
+ */
80
+ pure circuit TYPED_DATA_PREFIX(): Bytes<2> {
81
+ const raw: Vector<2, Uint<8>> = [25, 1];
82
+
83
+ return raw as Bytes<2>;
84
+ }
85
+
86
+ /**
87
+ * @description `keccak256("EIP712Domain(string name,string version,bytes32 salt)")`.
88
+ *
89
+ * @dev Written as a literal word. `compactc` emits a full keccak
90
+ * permutation even for a constant argument, so hashing the type string
91
+ * in-circuit would roughly double `domainSeparator`. The spec checks this
92
+ * word against the type string above.
93
+ *
94
+ * The string must match the domain's field set exactly: a difference of one
95
+ * character yields a different separator and every signature fails.
96
+ *
97
+ * @returns {Bytes<32>} The domain type hash.
98
+ */
99
+ pure circuit DOMAIN_TYPEHASH(): Bytes<32> {
100
+ // keccak256("EIP712Domain(string name,string version,bytes32 salt)")
101
+ // = 0x599a80fcaa47b95e2323ab4d34d34e0cc9feda4b843edafcc30c7bdf60ea15bf
102
+ const typeHash: Vector<32, Uint<8>> = [
103
+ 0x59, 0x9a, 0x80, 0xfc, 0xaa, 0x47, 0xb9, 0x5e,
104
+ 0x23, 0x23, 0xab, 0x4d, 0x34, 0xd3, 0x4e, 0x0c,
105
+ 0xc9, 0xfe, 0xda, 0x4b, 0x84, 0x3e, 0xda, 0xfc,
106
+ 0xc3, 0x0c, 0x7b, 0xdf, 0x60, 0xea, 0x15, 0xbf
107
+ ];
108
+
109
+ return typeHash as Bytes<32>;
110
+ }
111
+
112
+ /**
113
+ * @description Computes the domain separator for `(name, version, salt)`.
114
+ *
115
+ * EIP-712 encodes a `string` field as the keccak hash of its UTF-8 bytes,
116
+ * so the name and version arrive already hashed. Compact has no dynamic
117
+ * types, so a consumer hashes its own fixed-length literals.
118
+ *
119
+ * @constraints k=14, rows=4561 (zkir-v3)
120
+ *
121
+ * @param {Bytes<32>} hashedName - `keccak256(bytes(name))`.
122
+ * @param {Bytes<32>} hashedVersion - `keccak256(bytes(version))`.
123
+ * @param {Bytes<32>} salt - Per-deployment, per-network random value.
124
+ * @returns {Bytes<32>} The domain separator.
125
+ */
126
+ export pure circuit domainSeparator(
127
+ hashedName: Bytes<32>,
128
+ hashedVersion: Bytes<32>,
129
+ salt: Bytes<32>
130
+ ): Bytes<32> {
131
+ return keccak256<DomainFields>(DomainFields {
132
+ typeHash: DOMAIN_TYPEHASH(),
133
+ hashedName: hashedName,
134
+ hashedVersion: hashedVersion,
135
+ salt: salt
136
+ });
137
+ }
138
+
139
+ /**
140
+ * @description Wraps a struct hash in the typed-data envelope, yielding the
141
+ * digest an EVM signer actually signs.
142
+ *
143
+ * @constraints k=14, rows=4463 (zkir-v3)
144
+ *
145
+ * Requirements:
146
+ *
147
+ * - `separator` must be non-zero, which an initialized domain always is.
148
+ *
149
+ * @param {Bytes<32>} separator - The domain separator, fixed at deployment.
150
+ * @param {Bytes<32>} structHash - `keccak256(typeHash || the operation's ABI words)`.
151
+ * @returns {Bytes<32>} The digest to verify signatures against.
152
+ */
153
+ export pure circuit hashTypedData(
154
+ separator: Bytes<32>,
155
+ structHash: Bytes<32>
156
+ ): Bytes<32> {
157
+ // A zero separator is what an uninitialized ledger cell reads as, and a
158
+ // digest built from one still verifies signatures. Fail closed rather than
159
+ // authorize an operation under an unset domain.
160
+ assert(separator != pad(32, ""), "Eip712: domain separator not set");
161
+
162
+ return keccak256<TypedDataPreimage>(TypedDataPreimage {
163
+ prefixBytes: TYPED_DATA_PREFIX(),
164
+ domainSeparator: separator,
165
+ structHash: structHash
166
+ });
167
+ }
168
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0-alpha.1 (crypto/ElGamal.compact)
2
+ // OpenZeppelin Compact Contracts v0.4.0-alpha.3 (crypto/ElGamal.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5
 
@@ -38,6 +38,13 @@ pragma language_version >= 0.26.0;
38
38
  * cast reduces modulo the subgroup order, so casting at a call site would
39
39
  * silently accept an out-of-range scalar the typed parameter rejects.
40
40
  *
41
+ * @dev Domain separation. `persistentHash<Vector<N, Bytes<32>>>` is NOT
42
+ * domain-separating on its own. Two circuits hashing the same arity and
43
+ * element type share one hash domain, and the hash cannot tell which slot was
44
+ * "meant" as a tag. `secretToScalar` therefore prefixes a fixed constant, and
45
+ * that constant is placed FIRST so no call to `expandRandomness` (whose two
46
+ * slots are both caller-supplied) can reproduce the tuple by ordinary use.
47
+ *
41
48
  * @dev TRUST ASSUMPTION — subgroup membership. This is the module's most
42
49
  * load-bearing assumption. Every `JubjubPoint` reaching a curve operation is
43
50
  * assumed to be in the Jubjub prime-order subgroup, including points supplied by
@@ -105,11 +112,34 @@ module ElGamal {
105
112
  * @description Maps a 32-byte secret to a valid Jubjub scalar. See the
106
113
  * module-level hash-to-scalar note for why `degradeToTransient` is required.
107
114
  *
115
+ * @dev Domain separation here is load-bearing, not cosmetic. This output is
116
+ * the private key protecting every ciphertext held under an account. Public
117
+ * account identifiers derived from `Utils.computeAccountId`, for example,
118
+ * are computed as `persistentHash([secretKey])`, and that identifier
119
+ * is stored in the clear as a ledger map key. Untagged, this circuit
120
+ * would compute that same hash, so a secret used in both roles would
121
+ * have its encryption key recoverable from a published identifier by applying
122
+ * `degradeToTransient`. The tag makes the two derivations unrelated, so a
123
+ * wallet MAY safely derive its account secret and its encryption secret from
124
+ * common key material.
125
+ *
126
+ * @dev The tag is FIRST, deliberately. `expandRandomness` hashes
127
+ * `[seed, tag]` with both slots caller-supplied, so tagging this circuit as
128
+ * `[secret, tag]` would make `secretToScalar(x)` identical to
129
+ * `expandRandomness(x, <this tag>)` reachable by passing this domain string
130
+ * to the parameter that exists to receive domain strings. Leading with the
131
+ * tag means reproducing this output through `expandRandomness` would require
132
+ * passing the domain constant as the *seed*. Do not normalise the orderings.
133
+ *
108
134
  * @param secret - The 32-byte secret to map.
109
135
  * @return A valid Jubjub scalar.
110
136
  */
111
137
  export pure circuit secretToScalar(secret: Bytes<32>): JubjubScalar {
112
- return degradeToTransient(persistentHash<Vector<1, Bytes<32>>>([secret])) as JubjubScalar;
138
+ return degradeToTransient(
139
+ persistentHash<Vector<2, Bytes<32>>>(
140
+ [pad(32, "ElGamal:secretToScalar"), secret]
141
+ )
142
+ ) as JubjubScalar;
113
143
  }
114
144
 
115
145
  /**
@@ -419,6 +449,17 @@ module ElGamal {
419
449
  * wrong plaintext fails the decryption check. Lifted special case of
420
450
  * `assertDecryptsToPoint` with the message point `g^claimedValue`.
421
451
  *
452
+ * @notice `claimedValue` is the widest `Uint` Compact offers, so this parameter
453
+ * never bounds a caller. A narrower value costs proportionally fewer rows: the
454
+ * range constraint belongs to where the value is declared, not to this
455
+ * signature.
456
+ *
457
+ * @warning The claim domain must stay below the Jubjub subgroup order `l`:
458
+ * verification compares `g^claimedValue`, so two claims congruent mod `l` are
459
+ * indistinguishable. `Uint<248>` clears `l` (~2^251.86) by under 4 bits, and
460
+ * that width is set by Compact's cap rather than by this bound. 251 is the
461
+ * widest sound `Uint` if the cap ever rises.
462
+ *
422
463
  * Requirements:
423
464
  *
424
465
  * - `ek` is the secret for `pk`.
@@ -433,7 +474,7 @@ module ElGamal {
433
474
  ct: Ciphertext,
434
475
  pk: JubjubPoint,
435
476
  ek: Bytes<32>,
436
- claimedValue: Uint<128>
477
+ claimedValue: Uint<248>
437
478
  ): [] {
438
479
  assertDecryptsToPoint(ct, pk, ek, ecMulGenerator(claimedValue as JubjubScalar));
439
480
  }