@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
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ This project is built on the Midnight Network.
|
|
|
10
10
|
|
|
11
11
|
> ## ⚠️ WARNING! ⚠️
|
|
12
12
|
>
|
|
13
|
-
>
|
|
13
|
+
> Pre-release versions (`-alpha`, `-rc`) contain experimental, unaudited code.
|
|
14
14
|
> Expect rapid iteration.
|
|
15
15
|
> **Use at your own risk.**
|
|
16
16
|
|
|
@@ -193,7 +193,7 @@ ZKIR v3: the library targets ZKIR v2, the compiler 0.34.0 default.
|
|
|
193
193
|
Pass `--feature-zkir-v3` only when the contract reaches a module that needs it:
|
|
194
194
|
the secp256k1-based `crypto/Ecdsa` and `multisig/Secp256k1EcdsaMultisig`,
|
|
195
195
|
or the `keccak256`-based `crypto/Eip712`.
|
|
196
|
-
`multisig/presets/
|
|
196
|
+
`multisig/presets/NativeShieldedStatelessTreasury`, `multisig/presets/NativeShieldedTokenIssuer`
|
|
197
197
|
and the `multisig/examples/` contracts reach both.
|
|
198
198
|
`utils/EvmAbi` needs no `keccak256` of its own and compiles on either backend.
|
|
199
199
|
Compiling the other modules under v3 currently hits upstream blockers,
|
|
@@ -233,6 +233,8 @@ yarn compile
|
|
|
233
233
|
yarn test
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
+
Integration and live suites are described in [TESTING.md](TESTING.md).
|
|
237
|
+
|
|
236
238
|
### Check/apply Biome formatter
|
|
237
239
|
|
|
238
240
|
```bash
|
|
@@ -247,6 +249,7 @@ yarn lint:fix
|
|
|
247
249
|
```bash
|
|
248
250
|
yarn compile:access
|
|
249
251
|
yarn compile:archive
|
|
252
|
+
yarn compile:math
|
|
250
253
|
...
|
|
251
254
|
```
|
|
252
255
|
|
|
@@ -278,9 +281,11 @@ rm -rf .turbo/
|
|
|
278
281
|
|
|
279
282
|
## Security
|
|
280
283
|
|
|
281
|
-
|
|
284
|
+
For production, use a stable release (no `-alpha` or `-rc` suffix) on the Midnight ledger it targets. Pre-releases are not audited and should not be used in production.
|
|
285
|
+
|
|
286
|
+
Scopes and audited commits are listed in [audits](./audits/README.md).
|
|
282
287
|
|
|
283
|
-
Please report any security issues you find to <security@openzeppelin.com>.
|
|
288
|
+
Please report any security issues you find to <security@openzeppelin.com>. See [SECURITY.md](SECURITY.md).
|
|
284
289
|
|
|
285
290
|
### Provenance
|
|
286
291
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (access/AccessControl.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -61,18 +61,17 @@ pragma language_version >= 0.26.0;
|
|
|
61
61
|
* through the main role approval circuits (`grantRole` and `_grantRole`).
|
|
62
62
|
* In other words, role approvals to contract addresses are disallowed through these
|
|
63
63
|
* circuits.
|
|
64
|
-
* This is because
|
|
65
|
-
*
|
|
66
|
-
* circuit.
|
|
64
|
+
* This is because this library has not yet migrated to contract-to-contract calls, which means
|
|
65
|
+
* a contract granted a role cannot directly call the protected circuit.
|
|
67
66
|
*
|
|
68
67
|
* @notice This module does offer an experimental circuit that allows roles to be granted
|
|
69
68
|
* to contract addresses (`_unsafeGrantRole`).
|
|
70
69
|
* Note that the circuit name is very explicit ("unsafe") with this experimental circuit.
|
|
71
|
-
* Until contract-to-contract calls
|
|
70
|
+
* Until this library migrates to contract-to-contract calls,
|
|
72
71
|
* there is no direct way for a contract to call protected circuits.
|
|
73
72
|
*
|
|
74
|
-
* @notice The unsafe circuits are planned to become deprecated once
|
|
75
|
-
*
|
|
73
|
+
* @notice The unsafe circuits are planned to become deprecated once this library migrates to
|
|
74
|
+
* contract-to-contract calls.
|
|
76
75
|
*
|
|
77
76
|
* @dev Migration plan (safe -> C2C-capable role grants)
|
|
78
77
|
*
|
|
@@ -84,8 +83,8 @@ pragma language_version >= 0.26.0;
|
|
|
84
83
|
* which is the only kind of upgrade the CMA supports. Widening the type later would instead force
|
|
85
84
|
* a ledger-layout rewrite, which the CMA cannot perform.
|
|
86
85
|
*
|
|
87
|
-
* When C2C lands, an already-deployed contract migrates without changing its address
|
|
88
|
-
* state:
|
|
86
|
+
* When the C2C migration lands, an already-deployed contract migrates without changing its address
|
|
87
|
+
* or ledger state:
|
|
89
88
|
*
|
|
90
89
|
* - Minor release: mark `_unsafeGrantRole` deprecated (and emit a warning if possible); keep its
|
|
91
90
|
* implementation intact so existing callers continue to work.
|
package/access/Ownable.compact
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (access/Ownable.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -28,18 +28,17 @@ pragma language_version >= 0.26.0;
|
|
|
28
28
|
* through the main transfer circuits (`transferOwnership` and `_transferOwnership`).
|
|
29
29
|
* In other words, ownership transfers to contract addresses are disallowed through these
|
|
30
30
|
* circuits.
|
|
31
|
-
* This is because
|
|
32
|
-
*
|
|
33
|
-
* circuit.
|
|
31
|
+
* This is because this library has not yet migrated to contract-to-contract calls, which means
|
|
32
|
+
* a contract granted ownership cannot directly call the protected circuit.
|
|
34
33
|
*
|
|
35
34
|
* @notice This module does offer experimental circuits that allow ownership to be granted
|
|
36
35
|
* to contract addresses (`_unsafeTransferOwnership` and `_unsafeUncheckedTransferOwnership`).
|
|
37
36
|
* Note that the circuit names are very explicit ("unsafe") with these experimental circuits.
|
|
38
|
-
* Until contract-to-contract calls
|
|
37
|
+
* Until this library migrates to contract-to-contract calls, there is no direct way for a contract
|
|
39
38
|
* to authenticate as an owner.
|
|
40
39
|
*
|
|
41
|
-
* @notice The unsafe circuits are planned to become deprecated once
|
|
42
|
-
*
|
|
40
|
+
* @notice The unsafe circuits are planned to become deprecated once this library migrates to
|
|
41
|
+
* contract-to-contract calls.
|
|
43
42
|
*
|
|
44
43
|
* @dev Migration plan (safe -> C2C-capable ownership transfers)
|
|
45
44
|
*
|
|
@@ -51,8 +50,8 @@ pragma language_version >= 0.26.0;
|
|
|
51
50
|
* which is the only kind of upgrade the CMA supports. Widening the type later would instead force
|
|
52
51
|
* a ledger-layout rewrite, which the CMA cannot perform.
|
|
53
52
|
*
|
|
54
|
-
* When C2C lands, an already-deployed contract migrates without changing its address
|
|
55
|
-
* state:
|
|
53
|
+
* When the C2C migration lands, an already-deployed contract migrates without changing its address
|
|
54
|
+
* or ledger state:
|
|
56
55
|
*
|
|
57
56
|
* - Minor release: mark `_unsafeTransferOwnership` (and `_unsafeUncheckedTransferOwnership`)
|
|
58
57
|
* deprecated (and emit a warning if possible); keep their implementation intact so existing
|
|
@@ -117,6 +116,8 @@ module Ownable {
|
|
|
117
116
|
* @description Initializes the contract by setting the `initialOwner`.
|
|
118
117
|
* This must be called in the contract's constructor.
|
|
119
118
|
*
|
|
119
|
+
* @constraints k=10, rows=626
|
|
120
|
+
*
|
|
120
121
|
* Requirements:
|
|
121
122
|
*
|
|
122
123
|
* - Contract is not already initialized.
|
|
@@ -178,8 +179,8 @@ module Ownable {
|
|
|
178
179
|
/**
|
|
179
180
|
* @description Transfers ownership of the contract to `newOwner`.
|
|
180
181
|
*
|
|
181
|
-
* @notice Ownership transfers to contract addresses are
|
|
182
|
-
*
|
|
182
|
+
* @notice Ownership transfers to contract addresses are disallowed until this library migrates to
|
|
183
|
+
* contract-to-contract calls.
|
|
183
184
|
* This restriction prevents permanently disabling access to a circuit.
|
|
184
185
|
*
|
|
185
186
|
* @constraints k=13, rows=2947
|
|
@@ -204,10 +205,10 @@ module Ownable {
|
|
|
204
205
|
/**
|
|
205
206
|
* @description Unsafe variant of `transferOwnership`.
|
|
206
207
|
*
|
|
207
|
-
* @warning Ownership transfers to contract addresses are considered unsafe because
|
|
208
|
-
*
|
|
208
|
+
* @warning Ownership transfers to contract addresses are considered unsafe because this library
|
|
209
|
+
* has not yet migrated to contract-to-contract calls.
|
|
209
210
|
* Ownership privileges sent to a contract address may become uncallable.
|
|
210
|
-
* Once contract-to-contract calls
|
|
211
|
+
* Once this library migrates to contract-to-contract calls, this circuit may be deprecated.
|
|
211
212
|
*
|
|
212
213
|
* @constraints k=13, rows=2944
|
|
213
214
|
*
|
|
@@ -246,7 +247,7 @@ module Ownable {
|
|
|
246
247
|
assertOnlyOwner();
|
|
247
248
|
|
|
248
249
|
// We use `left` here because `assertOnlyOwner` would return a misleading
|
|
249
|
-
// error msg (
|
|
250
|
+
// error msg (this library has not yet migrated to c2c)
|
|
250
251
|
const zero = Either<Bytes<32>, ContractAddress> {
|
|
251
252
|
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
252
253
|
};
|
|
@@ -282,8 +283,8 @@ module Ownable {
|
|
|
282
283
|
* @description Transfers ownership of the contract to `newOwner` without
|
|
283
284
|
* enforcing permission checks on the caller.
|
|
284
285
|
*
|
|
285
|
-
* @notice Ownership transfers to contract addresses are
|
|
286
|
-
*
|
|
286
|
+
* @notice Ownership transfers to contract addresses are disallowed until this library migrates to
|
|
287
|
+
* contract-to-contract calls.
|
|
287
288
|
* This restriction prevents circuits from being inadvertently locked in contracts.
|
|
288
289
|
*
|
|
289
290
|
* @constraints k=10, rows=598
|
|
@@ -306,10 +307,10 @@ module Ownable {
|
|
|
306
307
|
/**
|
|
307
308
|
* @description Unsafe variant of `_transferOwnership`.
|
|
308
309
|
*
|
|
309
|
-
* @warning Ownership transfers to contract addresses are considered unsafe because
|
|
310
|
-
*
|
|
310
|
+
* @warning Ownership transfers to contract addresses are considered unsafe because this library
|
|
311
|
+
* has not yet migrated to contract-to-contract calls.
|
|
311
312
|
* Ownership privileges sent to a contract address may become uncallable.
|
|
312
|
-
* Once contract-to-contract calls
|
|
313
|
+
* Once this library migrates to contract-to-contract calls, this circuit may be deprecated.
|
|
313
314
|
*
|
|
314
315
|
* @constraints k=10, rows=595
|
|
315
316
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (access/ShieldedAccessControl.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -140,8 +140,14 @@ pragma language_version >= 0.26.0;
|
|
|
140
140
|
* between roles should use separate keys per role and accept the additional key management overhead.
|
|
141
141
|
* In the event of key compromise, the user should coordinate with admins to revoke all roles
|
|
142
142
|
* under the compromised `accountId` and re-grant under a new identity.
|
|
143
|
-
* -
|
|
144
|
-
*
|
|
143
|
+
* - Admins can revoke `(role, accountId)` pairings that were never granted, since on-chain
|
|
144
|
+
* non-membership proofs are not available for the Merkle tree. This permanently burns the
|
|
145
|
+
* nullifier for that pairing, blocking any future grant. Admin trust is a fundamental
|
|
146
|
+
* assumption of this system. Off-chain validation should confirm that a pairing was actually
|
|
147
|
+
* granted before submitting a revocation transaction.
|
|
148
|
+
* - `renounceRole` allows burning nullifiers for roles the caller does not currently hold,
|
|
149
|
+
* provided they possess the correct `secretKey`. This requires knowledge of the secret key,
|
|
150
|
+
* at which point the `accountId` should be considered fully compromised regardless.
|
|
145
151
|
* - The Merkle tree capacity of 2^20 leaf slots is a permanent, irrecoverable resource.
|
|
146
152
|
* There is no mechanism to reclaim slots after revocation, no upgrade path, and no emergency
|
|
147
153
|
* recovery. Once exhausted, the contract is permanently unable to grant new roles. Deployers
|
|
@@ -206,8 +212,7 @@ module ShieldedAccessControl {
|
|
|
206
212
|
|
|
207
213
|
/**
|
|
208
214
|
* @description Nullifiers of every pairing ever granted. Makes a repeated grant a no-op so
|
|
209
|
-
* one pairing can never consume more than one `_operatorRoles` slot
|
|
210
|
-
* so a nullifier can only exist for a granted pairing.
|
|
215
|
+
* one pairing can never consume more than one `_operatorRoles` slot.
|
|
211
216
|
* @type {Set<RoleNullifier>} _grantedRoleNullifiers
|
|
212
217
|
*/
|
|
213
218
|
export ledger _grantedRoleNullifiers: Set<RoleNullifier>;
|
|
@@ -469,17 +474,15 @@ module ShieldedAccessControl {
|
|
|
469
474
|
* @notice Roles are often managed via {grantRole} and {revokeRole}: this circuit's
|
|
470
475
|
* purpose is to provide a mechanism for accounts to lose their privileges
|
|
471
476
|
* if they are compromised (such as when a trusted device is misplaced).
|
|
472
|
-
* Consumers with no need for self-revocation should not export this circuit.
|
|
473
477
|
*
|
|
474
478
|
* @warning Outside observers may be able to use timing and pattern analysis to weaken pseudonymity
|
|
475
479
|
* guarantees if renounceRole is used in tandem with other on-chain actions.
|
|
476
480
|
*
|
|
477
|
-
* @constraints k=14, rows=
|
|
481
|
+
* @constraints k=14, rows=14440
|
|
478
482
|
*
|
|
479
483
|
* Requirements:
|
|
480
484
|
*
|
|
481
485
|
* - The caller must provide a valid `accountId` for the role.
|
|
482
|
-
* - The `(role, accountId)` pairing must have been granted.
|
|
483
486
|
* - The `(role, accountId)` pairing must not be already revoked.
|
|
484
487
|
* - Contract is initialized.
|
|
485
488
|
*
|
|
@@ -505,15 +508,15 @@ module ShieldedAccessControl {
|
|
|
505
508
|
/**
|
|
506
509
|
* @description Permanently revokes `role` from `accountId` by inserting a role nullifier into the
|
|
507
510
|
* `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
|
|
508
|
-
* `role`.
|
|
511
|
+
* `role`. At this time, proofs of non-membership on values in the Merkle tree LDT are not possible
|
|
512
|
+
* so a `(role, accountId)` pairing that does not exist can still be revoked.
|
|
509
513
|
*
|
|
510
|
-
* @constraints k=15, rows=
|
|
514
|
+
* @constraints k=15, rows=27005
|
|
511
515
|
*
|
|
512
516
|
* Requirements:
|
|
513
517
|
*
|
|
514
518
|
* - caller must prove they're an admin for `role`.
|
|
515
519
|
* - caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
|
|
516
|
-
* - The `(role, accountId)` pairing must have been granted.
|
|
517
520
|
* - The `(role, accountId)` pairing must not be already revoked.
|
|
518
521
|
* - Contract is initialized.
|
|
519
522
|
*
|
|
@@ -538,18 +541,18 @@ module ShieldedAccessControl {
|
|
|
538
541
|
/**
|
|
539
542
|
* @description Permanently revokes `role` from `accountId` by inserting a role nullifier into the
|
|
540
543
|
* `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
|
|
541
|
-
* `role`.
|
|
544
|
+
* `role`. At this time, proofs of non-membership on values in the Merkle tree LDT are not possible
|
|
545
|
+
* so a `(role, accountId)` pairing that does not exist can still be revoked.
|
|
542
546
|
*
|
|
543
547
|
* Internal circuit without access restriction.
|
|
544
548
|
*
|
|
545
549
|
* @warning Exposing this circuit directly in an implementing contract would allow anyone to revoke
|
|
546
550
|
* roles without authorization. It must be wrapped with appropriate access control.
|
|
547
551
|
*
|
|
548
|
-
* @constraints k=14, rows=
|
|
552
|
+
* @constraints k=14, rows=10260
|
|
549
553
|
*
|
|
550
554
|
* Requirements:
|
|
551
555
|
*
|
|
552
|
-
* - The `(role, accountId)` pairing must have been granted.
|
|
553
556
|
* - The `(role, accountId)` pairing must not be already revoked.
|
|
554
557
|
* - Contract is initialized.
|
|
555
558
|
*
|
|
@@ -571,8 +574,8 @@ module ShieldedAccessControl {
|
|
|
571
574
|
* @description Core business logic for the grant/revoke role circuits. Asserts that the
|
|
572
575
|
* `(role, accountId)` pairing has not already been revoked. On success, dispatches on
|
|
573
576
|
* `updateType`: a `Grant` inserts the role commitment into `_operatorRoles` unless the
|
|
574
|
-
* nullifier is already in `_grantedRoleNullifiers`, and a `Revoke`
|
|
575
|
-
*
|
|
577
|
+
* nullifier is already in `_grantedRoleNullifiers`, and a `Revoke` inserts the nullifier
|
|
578
|
+
* into `_roleCommitmentNullifiers`.
|
|
576
579
|
*
|
|
577
580
|
* Disclosures:
|
|
578
581
|
*
|
|
@@ -600,14 +603,12 @@ module ShieldedAccessControl {
|
|
|
600
603
|
const isRevoked = _roleCommitmentNullifiers.member(disclose(roleNullifier));
|
|
601
604
|
assert(!isRevoked, "ShieldedAccessControl: role is already revoked");
|
|
602
605
|
|
|
603
|
-
const isGranted = _grantedRoleNullifiers.member(disclose(roleNullifier));
|
|
604
606
|
if (updateType == UpdateType.Grant) {
|
|
605
|
-
if (!
|
|
607
|
+
if (!_grantedRoleNullifiers.member(disclose(roleNullifier))) {
|
|
606
608
|
_grantedRoleNullifiers.insert(disclose(roleNullifier));
|
|
607
609
|
_operatorRoles.insert(disclose(roleCommitment));
|
|
608
610
|
}
|
|
609
611
|
} else {
|
|
610
|
-
assert(isGranted, "ShieldedAccessControl: role was never granted");
|
|
611
612
|
_roleCommitmentNullifiers.insert(disclose(roleNullifier));
|
|
612
613
|
}
|
|
613
614
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (access/ZOwnablePK.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -15,8 +15,8 @@ pragma language_version >= 0.26.0;
|
|
|
15
15
|
* For the strongest security guarantees, use an Air-Gapped Public Key.
|
|
16
16
|
*
|
|
17
17
|
* @notice This module explicitly supports commitments derived from public keys;
|
|
18
|
-
* however, it may be possible to use contract addresses
|
|
19
|
-
* calls
|
|
18
|
+
* however, it may be possible to use contract addresses once this library migrates to
|
|
19
|
+
* contract-to-contract calls. This will be revisited when it's known if/how witnesses
|
|
20
20
|
* are used from a contract address context.
|
|
21
21
|
*
|
|
22
22
|
* @dev Features:
|
|
@@ -106,6 +106,8 @@ module ZOwnablePK {
|
|
|
106
106
|
* @warning The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm.
|
|
107
107
|
* Using any other algorithm will result in a permanent loss of contract access.
|
|
108
108
|
*
|
|
109
|
+
* @constraints k=13, rows=6765
|
|
110
|
+
*
|
|
109
111
|
* Requirements:
|
|
110
112
|
*
|
|
111
113
|
* - Contract is not initialized.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (crypto/Ecdh.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.26.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module Ecdh
|
|
8
|
+
* @description Stateless Diffie-Hellman key agreement over Jubjub. Splits into
|
|
9
|
+
* the two halves of one exchange: `deriveShared` runs the sender side and
|
|
10
|
+
* returns the ephemeral public key to publish alongside the shared secret, and
|
|
11
|
+
* `recoverShared` runs the recipient side from that published point. The module
|
|
12
|
+
* agrees on a point and nothing more: deriving keys, masks or nonces from it
|
|
13
|
+
* belongs to the consumer (`crypto/EcdhMask` for hybrid encryption).
|
|
14
|
+
*
|
|
15
|
+
* @see https://www.secg.org/sec1-v2.pdf#page=33 SEC 1 v2.0 §3.3.1, Elliptic Curve Diffie-Hellman Primitive
|
|
16
|
+
* @see https://www.secg.org/sec1-v2.pdf#page=30 SEC 1 v2.0 §3.2.2.1, Elliptic Curve Public Key Validation Primitive
|
|
17
|
+
* @see https://www.secg.org/sec1-v2.pdf#page=29 SEC 1 v2.0 §3.2.1, Elliptic Curve Key Pair Generation Primitive
|
|
18
|
+
*
|
|
19
|
+
* @dev Construction and notation. SEC 1's symbol follows in brackets.
|
|
20
|
+
* - `g`: the Jubjub prime-order subgroup generator, of order `ℓ` [`G`, `n`].
|
|
21
|
+
* - `ek`: the recipient's secret scalar in `[1, ℓ-1]` [`d`, §3.2.1].
|
|
22
|
+
* - `e`: the sender's ephemeral scalar in `[1, ℓ-1]`, fresh per call [`d`, §3.2.1].
|
|
23
|
+
* - `pk = g^ek`: the recipient's public key [`Q`, §3.2.1].
|
|
24
|
+
* - `E = g^e`: the ephemeral public key, published [`Q`, §3.2.1].
|
|
25
|
+
* - `S = pk^e = g^{ek*e} = E^ek`: the shared secret point, never published
|
|
26
|
+
* [`P = d * Q`, §3.3.1]. §3.3.1 outputs `z = x_P`; this module returns `P`
|
|
27
|
+
* itself and leaves the KDF to the consumer.
|
|
28
|
+
* - `identity`: the point at infinity, `ecMulGenerator(0)` [`O`]. §3.2.2.1
|
|
29
|
+
* action 1 rejects it as a key; §3.3.1 action 2 rejects it as `P`.
|
|
30
|
+
* `deriveShared` does the first; the second cannot fire in a prime-order
|
|
31
|
+
* group once the first holds and `e ≢ 0 (mod ℓ)`.
|
|
32
|
+
* - Jubjub only. A second curve gets its own module with the same circuit names.
|
|
33
|
+
*
|
|
34
|
+
* @dev Future APIs.
|
|
35
|
+
* - `deriveEphemeral(seed: Bytes<32>, nonce: Field): JubjubScalar` — a canonical
|
|
36
|
+
* `e` for consumers, making the uniqueness half of the freshness rule true by
|
|
37
|
+
* construction rather than a precondition.
|
|
38
|
+
* - `assertValidPk(pk)` / `isIdentity(pk): Boolean` — reject an identity key
|
|
39
|
+
* where it is registered. The `deriveShared` guard fires on the sender's
|
|
40
|
+
* transaction, so a bad key costs the sender, not the registrant.
|
|
41
|
+
* - Jubjub key-pair derivation lives in `crypto/ElGamal` (`derivePk`,
|
|
42
|
+
* `secretToScalar`); a shared key module is its natural home.
|
|
43
|
+
*
|
|
44
|
+
* @dev Freshness and secrecy precondition (CRITICAL). `e` MUST be both unique per
|
|
45
|
+
* call to the same recipient AND secret (unpredictable to observers), even across
|
|
46
|
+
* circuit invocations that reuse a randomness seed.
|
|
47
|
+
*
|
|
48
|
+
* Uniqueness: a repeated `e` to one recipient repeats `S`, so every key, mask or
|
|
49
|
+
* nonce a consumer derives from `S` repeats too. Where that derivation is a
|
|
50
|
+
* one-time pad (`crypto/EcdhMask`) the reuse is catastrophic: subtracting two
|
|
51
|
+
* ciphertexts reveals the plaintext difference, and either plaintext once the
|
|
52
|
+
* other is known or constrained.
|
|
53
|
+
*
|
|
54
|
+
* Secrecy: `recipientPk` is public, so anyone who learns `e` recomputes
|
|
55
|
+
* `S = recipientPk^e` and everything derived from it. A unique but PREDICTABLE
|
|
56
|
+
* `e` (a plain counter, a public nonce) therefore breaks confidentiality
|
|
57
|
+
* completely. `e` must be high-entropy and secret, exactly like an ElGamal
|
|
58
|
+
* encryption randomness (see `crypto/ElGamal`), not merely distinct.
|
|
59
|
+
*
|
|
60
|
+
* The caller supplies `e` and owns both properties, e.g. by expanding a secret
|
|
61
|
+
* per-invocation seed bound to a per-recipient-unique nonce (a monotonic index).
|
|
62
|
+
* This is stronger than exponential ElGamal's uniqueness requirement (there,
|
|
63
|
+
* reuse merely leaks plaintext equality); do not treat `e` as "any randomness."
|
|
64
|
+
*
|
|
65
|
+
* @dev Weak inputs. Two degenerate inputs collapse `S` to the identity point,
|
|
66
|
+
* which every observer can compute: `recipientPk = identity` gives `S = identity`
|
|
67
|
+
* for any `e`, and `e = 0` gives `S = identity` for any key. Anything a consumer
|
|
68
|
+
* derives from such an `S` is public. The identity is a valid subgroup point, so
|
|
69
|
+
* the runtime subgroup guarantee does NOT catch it; `deriveShared` therefore
|
|
70
|
+
* asserts `recipientPk != identity` and `ephemeralPk (= g^e) != identity`, and
|
|
71
|
+
* every sender path reaches both through it. Guarding the ephemeral POINT rather
|
|
72
|
+
* than the scalar subsumes `e != 0` and stays correct regardless of how the
|
|
73
|
+
* runtime treats an out-of-range scalar: `g^e = identity` captures both `e = 0`
|
|
74
|
+
* and any `e ≡ 0 (mod ℓ)`. (Guarding `e != 0` directly would instead lean on the
|
|
75
|
+
* runtime faulting `ecMul` for scalars `≥ ℓ`; the point guard removes that
|
|
76
|
+
* dependency.) These are the only weak inputs (for a subgroup key,
|
|
77
|
+
* `S = identity` iff the ephemeral point is the identity).
|
|
78
|
+
*
|
|
79
|
+
* @dev Subgroup membership. Secrecy of `S` relies on the runtime constraining
|
|
80
|
+
* every `JubjubPoint` into the prime-order subgroup (argued in `crypto/ElGamal`),
|
|
81
|
+
* so a caller cannot hand in a low-order point that leaks `ekScalar`.
|
|
82
|
+
*
|
|
83
|
+
* @dev No ledger state, no witnesses. Both circuits are pure and take their keys
|
|
84
|
+
* and randomness as explicit arguments. `recoverShared` is exposed for
|
|
85
|
+
* wallet/off-chain use (recovering a received value needs no proof); the on-chain
|
|
86
|
+
* path only ever calls `deriveShared`.
|
|
87
|
+
*/
|
|
88
|
+
module Ecdh {
|
|
89
|
+
import CompactStandardLibrary;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @description One sender-side ECDH step: the ephemeral public key to publish
|
|
93
|
+
* and the shared secret point to derive from.
|
|
94
|
+
*
|
|
95
|
+
* @notice `sShared` is secret. Keep it a circuit local; disclosing it opens
|
|
96
|
+
* everything derived from it.
|
|
97
|
+
*/
|
|
98
|
+
export struct SharedSecret {
|
|
99
|
+
/** `E = g^e`, public. The recipient recovers `S = E^ek` from it. */
|
|
100
|
+
ephemeralPk: JubjubPoint;
|
|
101
|
+
|
|
102
|
+
/** `S = pk^e`, secret. Never disclose; derive from it in-circuit. */
|
|
103
|
+
sShared: JubjubPoint;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @description Runs the sender's ECDH step: the ephemeral public key `g^e` to
|
|
108
|
+
* publish and the shared secret `pk^e` to derive from. The single site of the
|
|
109
|
+
* weak-input guards.
|
|
110
|
+
*
|
|
111
|
+
* @notice `e` MUST be fresh per call and secret (see the module freshness
|
|
112
|
+
* precondition).
|
|
113
|
+
*
|
|
114
|
+
* @constraints k=11, rows=1385
|
|
115
|
+
*
|
|
116
|
+
* Requirements:
|
|
117
|
+
*
|
|
118
|
+
* - `recipientPk` is not the identity point.
|
|
119
|
+
* - `e` is non-zero, a valid Jubjub scalar (`< ℓ`), secret, and fresh per call.
|
|
120
|
+
*
|
|
121
|
+
* @param recipientPk - The recipient's Jubjub public key (`g^ek`).
|
|
122
|
+
* @param e - A fresh ephemeral scalar.
|
|
123
|
+
* @return `{ ephemeralPk = g^e, sShared = recipientPk^e }`.
|
|
124
|
+
*/
|
|
125
|
+
export pure circuit deriveShared(recipientPk: JubjubPoint, e: JubjubScalar): SharedSecret {
|
|
126
|
+
const identity = ecMulGenerator(0 as JubjubScalar);
|
|
127
|
+
// An identity key gives S = identity for every `e`.
|
|
128
|
+
assert(recipientPk != identity, "Ecdh: identity pk");
|
|
129
|
+
const ephemeralPk = ecMulGenerator(e);
|
|
130
|
+
// Guarding the POINT subsumes `e == 0` and any `e ≡ 0 (mod ℓ)` (see the
|
|
131
|
+
// weak-inputs note).
|
|
132
|
+
assert(ephemeralPk != identity, "Ecdh: zero ephemeral");
|
|
133
|
+
return SharedSecret { ephemeralPk: ephemeralPk, sShared: ecMul(recipientPk, e) };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @description The recipient's half of `deriveShared`: recomputes `S = E^ek`
|
|
138
|
+
* from a published ephemeral public key.
|
|
139
|
+
*
|
|
140
|
+
* @dev Never asserts. A wrong `ekScalar` yields an unrelated point, so a wallet
|
|
141
|
+
* scanning exchanges not addressed to it learns nothing from this circuit and
|
|
142
|
+
* the module exposes no key-correctness oracle.
|
|
143
|
+
*
|
|
144
|
+
* @constraints k=9, rows=440
|
|
145
|
+
*
|
|
146
|
+
* @param ephemeralPk - The sender's published `g^e`.
|
|
147
|
+
* @param ekScalar - The recipient's secret scalar.
|
|
148
|
+
* @return The ECDH shared secret point.
|
|
149
|
+
*/
|
|
150
|
+
export pure circuit recoverShared(ephemeralPk: JubjubPoint, ekScalar: JubjubScalar): JubjubPoint {
|
|
151
|
+
return ecMul(ephemeralPk, ekScalar);
|
|
152
|
+
}
|
|
153
|
+
}
|