@openzeppelin/compact-contracts 0.2.0 → 0.3.0-alpha.1
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 +10 -10
- package/access/AccessControl.compact +3 -35
- package/access/Ownable.compact +5 -51
- package/access/ShieldedAccessControl.compact +2 -2
- package/access/ZOwnablePK.compact +2 -2
- package/crypto/EcdhMask.compact +177 -0
- package/crypto/ElGamal.compact +473 -0
- package/multisig/ForwarderPrivate.compact +233 -0
- package/multisig/ForwarderShielded.compact +149 -0
- package/multisig/ForwarderUnshielded.compact +152 -0
- package/multisig/ProposalManager.compact +328 -0
- package/multisig/ShieldedTreasury.compact +204 -0
- package/multisig/ShieldedTreasuryStateless.compact +80 -0
- package/multisig/Signer.compact +312 -0
- package/multisig/SignerManager.compact +205 -0
- package/multisig/UnshieldedTreasury.compact +114 -0
- package/multisig/presets/ShieldedMultiSig.compact +227 -0
- package/multisig/presets/ShieldedMultiSigV2.compact +280 -0
- package/multisig/presets/ShieldedMultiSigV3.compact +368 -0
- package/multisig/presets/forwarder/ForwarderPrivate.compact +125 -0
- package/multisig/presets/forwarder/ForwarderShielded.compact +62 -0
- package/multisig/presets/forwarder/ForwarderUnshielded.compact +64 -0
- package/package.json +32 -16
- package/security/Allowlist.compact +101 -0
- package/security/Blocklist.compact +99 -0
- package/security/Initializable.compact +2 -2
- package/security/Pausable.compact +2 -2
- package/token/ConfidentialFungibleToken.compact +1180 -0
- package/token/FungibleToken.compact +24 -82
- package/token/MultiToken.compact +21 -80
- package/token/NativeShieldedToken.compact +285 -0
- package/token/NativeShieldedTokenCore.compact +343 -0
- package/token/NativeShieldedTokenFamily.compact +287 -0
- package/token/NonFungibleToken.compact +25 -83
- package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +90 -0
- package/token/extensions/NativeShieldedTokenDerivedNonce.compact +70 -0
- package/token/extensions/NativeShieldedTokenFamilySupply.compact +145 -0
- package/token/extensions/NativeShieldedTokenSupply.compact +142 -0
- package/token/extensions/NativeShieldedTokenSupplyCore.compact +163 -0
- package/utils/Utils.compact +86 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://docs.midnight.network/relnotes/compact/minokawa-0-18-26-0)
|
|
2
2
|
[](CODE_OF_CONDUCT.md)
|
|
3
3
|
|
|
4
4
|
This project is built on the Midnight Network.
|
|
@@ -37,8 +37,8 @@ Follow Midnight's [Compact Developer Tools installation guide](https://docs.midn
|
|
|
37
37
|
```bash
|
|
38
38
|
$ compact compile --version
|
|
39
39
|
|
|
40
|
-
Compactc version: 0.
|
|
41
|
-
0.
|
|
40
|
+
Compactc version: 0.31.0
|
|
41
|
+
0.31.0
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
### Installation
|
|
@@ -214,7 +214,7 @@ git clone git@github.com:OpenZeppelin/compact-contracts.git
|
|
|
214
214
|
```bash
|
|
215
215
|
nvm install && \
|
|
216
216
|
yarn && \
|
|
217
|
-
yarn
|
|
217
|
+
yarn compile
|
|
218
218
|
```
|
|
219
219
|
|
|
220
220
|
### Run tests
|
|
@@ -226,8 +226,8 @@ yarn test
|
|
|
226
226
|
### Check/apply Biome formatter
|
|
227
227
|
|
|
228
228
|
```bash
|
|
229
|
-
yarn
|
|
230
|
-
yarn
|
|
229
|
+
yarn lint
|
|
230
|
+
yarn lint:fix
|
|
231
231
|
```
|
|
232
232
|
|
|
233
233
|
### Advanced
|
|
@@ -235,8 +235,8 @@ yarn fmt-and-lint:fix
|
|
|
235
235
|
#### Targeted compilation
|
|
236
236
|
|
|
237
237
|
```bash
|
|
238
|
-
yarn
|
|
239
|
-
yarn
|
|
238
|
+
yarn compile:access
|
|
239
|
+
yarn compile:archive
|
|
240
240
|
...
|
|
241
241
|
```
|
|
242
242
|
|
|
@@ -246,10 +246,10 @@ ZK key generation is slow and usually unnecessary during development.
|
|
|
246
246
|
|
|
247
247
|
```bash
|
|
248
248
|
# Full compilation with skip-zk (use environment variable)
|
|
249
|
-
SKIP_ZK=true yarn
|
|
249
|
+
SKIP_ZK=true yarn compile
|
|
250
250
|
|
|
251
251
|
# Access compilation with skip-zk (this compiles security first as a dependency)
|
|
252
|
-
SKIP_ZK=true yarn
|
|
252
|
+
SKIP_ZK=true yarn compile:access
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
#### Clean environment
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (access/AccessControl.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module AccessControl
|
|
@@ -442,38 +442,6 @@ module AccessControl {
|
|
|
442
442
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
443
443
|
*/
|
|
444
444
|
circuit _computeAccountId(): Bytes<32> {
|
|
445
|
-
return
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
450
|
-
* their identity commitment before submitting it in a grant or revoke operation.
|
|
451
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
452
|
-
* given the same inputs.
|
|
453
|
-
*
|
|
454
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
455
|
-
* permanently compromise the privacy guarantees of this system:
|
|
456
|
-
*
|
|
457
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
458
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
459
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
460
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
461
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
462
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
463
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered. Back up
|
|
464
|
-
* keys securely before using them in role commitments.
|
|
465
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
466
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
467
|
-
* the key to a malicious observer.
|
|
468
|
-
*
|
|
469
|
-
* ## ID Derivation
|
|
470
|
-
* `accountId = persistentHash(secretKey)`
|
|
471
|
-
*
|
|
472
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
473
|
-
*
|
|
474
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
475
|
-
*/
|
|
476
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
477
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
445
|
+
return Utils_computeAccountId(wit_AccessControlSK());
|
|
478
446
|
}
|
|
479
447
|
}
|
package/access/Ownable.compact
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (access/Ownable.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module Ownable
|
|
@@ -125,7 +125,7 @@ module Ownable {
|
|
|
125
125
|
export circuit initialize(initialOwner: Either<Bytes<32>, ContractAddress>): [] {
|
|
126
126
|
assertNotInitialized();
|
|
127
127
|
_isInitialized = true;
|
|
128
|
-
assert(!
|
|
128
|
+
assert(!Utils_isTargetZero(initialOwner), "Ownable: invalid initial owner");
|
|
129
129
|
_transferOwnership(initialOwner);
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -219,7 +219,7 @@ module Ownable {
|
|
|
219
219
|
export circuit _unsafeTransferOwnership(newOwner: Either<Bytes<32>, ContractAddress>): [] {
|
|
220
220
|
assertInitialized();
|
|
221
221
|
assertOnlyOwner();
|
|
222
|
-
assert(!
|
|
222
|
+
assert(!Utils_isTargetZero(newOwner), "Ownable: invalid new owner");
|
|
223
223
|
_unsafeUncheckedTransferOwnership(newOwner);
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -335,52 +335,6 @@ module Ownable {
|
|
|
335
335
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
336
336
|
*/
|
|
337
337
|
circuit _computeAccountId(): Bytes<32> {
|
|
338
|
-
return
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
343
|
-
* their identity commitment before submitting it in an ownership transfer.
|
|
344
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
345
|
-
* given the same inputs.
|
|
346
|
-
*
|
|
347
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
348
|
-
* permanently compromise the security of this system:
|
|
349
|
-
*
|
|
350
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
351
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
352
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
353
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
354
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
355
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
356
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
357
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
358
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
359
|
-
* the key to a malicious observer.
|
|
360
|
-
*
|
|
361
|
-
* ## ID Derivation
|
|
362
|
-
* `accountId = persistentHash(secretKey)`
|
|
363
|
-
*
|
|
364
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
365
|
-
*
|
|
366
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
367
|
-
*/
|
|
368
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
369
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
374
|
-
* holds the zero value.
|
|
375
|
-
*
|
|
376
|
-
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
377
|
-
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
378
|
-
*/
|
|
379
|
-
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
380
|
-
if (target.is_left) {
|
|
381
|
-
return target.left == default<Bytes<32>>;
|
|
382
|
-
} else {
|
|
383
|
-
return target.right == default<ContractAddress>;
|
|
384
|
-
}
|
|
338
|
+
return Utils_computeAccountId(wit_OwnableSK());
|
|
385
339
|
}
|
|
386
340
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (access/ShieldedAccessControl.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module ShieldedAccessControl
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (access/ZOwnablePK.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module ZOwnablePK
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha.1 (crypto/EcdhMask.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module EcdhMask
|
|
8
|
+
* @description Stateless ECDH hybrid encryption of a single field element to a
|
|
9
|
+
* Jubjub public key. Delivers a value to whoever holds the secret behind a
|
|
10
|
+
* Jubjub public key WITHOUT encoding it in the exponent, so recovery needs no
|
|
11
|
+
* discrete-log search and the value is unbounded within `Uint<128>`. This is the
|
|
12
|
+
* direct-decrypt counterpart to the homomorphic `crypto/ElGamal`: use ElGamal to
|
|
13
|
+
* accumulate ciphertexts, use this to hand a recipient the plaintext.
|
|
14
|
+
*
|
|
15
|
+
* Construction (recipient key `pk = g^ek`, ephemeral scalar `e` fresh per call):
|
|
16
|
+
* E = g^e (ephemeral public key)
|
|
17
|
+
* S = pk^e = g^{ek*e} (ECDH shared secret point)
|
|
18
|
+
* mask = KDF(S) (domain-separated Field, see `kdf`)
|
|
19
|
+
* ct = value + mask (field one-time-pad)
|
|
20
|
+
* Recipient recovers: S = E^ek, mask = KDF(S), value = ct - mask.
|
|
21
|
+
*
|
|
22
|
+
* @dev Freshness and secrecy precondition (CRITICAL). `e` MUST be both unique per
|
|
23
|
+
* call to the same recipient AND secret (unpredictable to observers), even across
|
|
24
|
+
* circuit invocations that reuse a randomness seed.
|
|
25
|
+
*
|
|
26
|
+
* Uniqueness: the mask is a one-time pad, so a repeated `e` repeats `S` and
|
|
27
|
+
* `mask`, and reusing a pad is catastrophic (subtracting two ciphertexts recovers
|
|
28
|
+
* both values).
|
|
29
|
+
*
|
|
30
|
+
* Secrecy: `recipientPk` is public, so anyone who learns `e` recomputes
|
|
31
|
+
* `S = recipientPk^e`, hence the mask, and opens the ciphertext. A unique but
|
|
32
|
+
* PREDICTABLE `e` (a plain counter, a public nonce) therefore breaks
|
|
33
|
+
* confidentiality completely. `e` must be high-entropy and secret, exactly like
|
|
34
|
+
* an ElGamal encryption randomness (see `crypto/ElGamal`), not merely distinct.
|
|
35
|
+
*
|
|
36
|
+
* The caller supplies `e` and owns both properties, e.g. by expanding a secret
|
|
37
|
+
* per-invocation seed bound to a per-recipient-unique nonce (a monotonic index).
|
|
38
|
+
* This is stronger than exponential ElGamal's uniqueness requirement (there,
|
|
39
|
+
* reuse merely leaks plaintext equality); do not treat `e` as "any randomness."
|
|
40
|
+
*
|
|
41
|
+
* @dev Weak inputs. Two degenerate inputs zero the shared secret and expose the
|
|
42
|
+
* value: `recipientPk = identity` gives `S = identity` for any `e`, and `e = 0`
|
|
43
|
+
* gives `S = identity` for any key. Both yield `mask = KDF(identity, domain)`, a
|
|
44
|
+
* value any observer can recompute, so `ct = value + mask` opens trivially. The
|
|
45
|
+
* identity is a valid subgroup point, so the runtime subgroup guarantee does NOT
|
|
46
|
+
* catch it; `encrypt` therefore asserts `recipientPk != identity` and
|
|
47
|
+
* `ephemeralPk (= g^e) != identity`. Guarding the ephemeral POINT rather than the
|
|
48
|
+
* scalar subsumes `e != 0` and stays correct regardless of how the runtime treats
|
|
49
|
+
* an out-of-range scalar: `g^e = identity` captures both `e = 0` and any
|
|
50
|
+
* `e ≡ 0 (mod ℓ)`. (Guarding `e != 0` directly would instead lean on the runtime
|
|
51
|
+
* faulting `ecMul` for scalars `≥ ℓ`; the point guard removes that dependency.)
|
|
52
|
+
* These are the only weak inputs (for a subgroup key, `S = identity` iff the
|
|
53
|
+
* ephemeral point is the identity).
|
|
54
|
+
*
|
|
55
|
+
* @dev Integrity. The pad is malleable and NO authentication is added here. This
|
|
56
|
+
* is safe in the intended on-chain use, where the ciphertext is computed
|
|
57
|
+
* in-circuit from a value the same circuit commits to elsewhere (a producer
|
|
58
|
+
* cannot desync the two) and is immutable once written (no in-transit attacker).
|
|
59
|
+
* A corrupted ciphertext could only fail the recipient's own later proof, never
|
|
60
|
+
* enable theft. Consumers with a different threat model (mutable storage,
|
|
61
|
+
* in-transit tampering) MUST add their own authentication.
|
|
62
|
+
*
|
|
63
|
+
* @dev Security. This is hashed ElGamal (ECIES without a MAC) over Jubjub and
|
|
64
|
+
* inherits its guarantees: IND-CPA under the Oracle Diffie-Hellman assumption in
|
|
65
|
+
* the random-oracle model (the `persistentHash` KDF keyed by the secret point
|
|
66
|
+
* `S`). It is NOT IND-CCA: the additive pad is malleable (see Integrity), which
|
|
67
|
+
* is acceptable here only because the on-chain ciphertext is bound in-circuit to
|
|
68
|
+
* the value it commits to elsewhere. Hiding carries a large margin: `kdf` returns
|
|
69
|
+
* a `degradeToTransient` output in `[0, 2^248)` while `value < 2^128`, so even
|
|
70
|
+
* treating the KDF output as uniform the value is hidden with about `2^-120`
|
|
71
|
+
* statistical slack, leaving only the assumption that the hash of the DH secret
|
|
72
|
+
* is PRF-like. Confidentiality relies on the runtime constraining every
|
|
73
|
+
* `JubjubPoint` into the prime-order subgroup (see `crypto/ElGamal`), so `S`
|
|
74
|
+
* cannot be a low-order point that leaks `ek`. Binding the ephemeral `E` into the
|
|
75
|
+
* KDF is deliberately omitted: for a fixed `recipientPk`, `E = g^e` and
|
|
76
|
+
* `S = pk^e` are both determined by `e`, so `KDF(S)` already varies with `E` and
|
|
77
|
+
* folding it in buys nothing.
|
|
78
|
+
*
|
|
79
|
+
* @dev Shared key with `crypto/ElGamal` (in scope). A consumer MAY use the same
|
|
80
|
+
* recipient Jubjub key for both these memos (hashed ElGamal) and exponential-
|
|
81
|
+
* ElGamal balance ciphertexts, as the confidential token does. This joint use is
|
|
82
|
+
* safe: the two schemes share only the public key, this KDF is domain-separated
|
|
83
|
+
* and keyed by the secret DH point, and neither exposes a decryption oracle, so
|
|
84
|
+
* under CPA / ODH a ciphertext of one scheme does not help attack the other.
|
|
85
|
+
*
|
|
86
|
+
* @dev No ledger state, no witnesses. Every circuit is pure and takes its keys,
|
|
87
|
+
* value, and randomness as explicit arguments. `decrypt` is exposed as a pure
|
|
88
|
+
* circuit for wallet/off-chain use (recovering a received value needs no proof);
|
|
89
|
+
* the on-chain path only ever calls `encrypt`.
|
|
90
|
+
*/
|
|
91
|
+
module EcdhMask {
|
|
92
|
+
import CompactStandardLibrary;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @description An ECDH one-time-pad ciphertext: the ephemeral public key and
|
|
96
|
+
* the field-masked value.
|
|
97
|
+
*/
|
|
98
|
+
export struct Ciphertext {
|
|
99
|
+
ephemeralPk: JubjubPoint;
|
|
100
|
+
ct: Field;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @description Derives the field mask from the ECDH shared secret point,
|
|
105
|
+
* domain-separated by a caller-supplied `domain` tag. Hashes the point to
|
|
106
|
+
* `Bytes<32>`, then re-hashes it with the domain tag and truncates into the
|
|
107
|
+
* field via `degradeToTransient`. The consumer chooses `domain` (so this
|
|
108
|
+
* module is not tied to any one protocol) and MUST use the same value on
|
|
109
|
+
* encrypt and decrypt.
|
|
110
|
+
*
|
|
111
|
+
* @dev Uses `persistentHash` deliberately: the recipient reproduces this mask
|
|
112
|
+
* to decrypt, possibly across a platform upgrade, so it must be upgrade-stable.
|
|
113
|
+
*
|
|
114
|
+
* @param sShared - The ECDH shared secret point (`pk^e` for the sender,
|
|
115
|
+
* `E^ek` for the recipient; equal by construction).
|
|
116
|
+
* @param domain - The consumer's domain-separation tag.
|
|
117
|
+
* @return The field mask.
|
|
118
|
+
*/
|
|
119
|
+
export pure circuit kdf(sShared: JubjubPoint, domain: Bytes<32>): Field {
|
|
120
|
+
const pointHash = persistentHash<JubjubPoint>(sShared);
|
|
121
|
+
return degradeToTransient(
|
|
122
|
+
persistentHash<Vector<2, Bytes<32>>>([pointHash, domain])
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @description Encrypts `value` to `recipientPk` under ephemeral scalar `e`.
|
|
128
|
+
*
|
|
129
|
+
* @notice `e` MUST be fresh per call (see the module freshness precondition).
|
|
130
|
+
*
|
|
131
|
+
* Requirements:
|
|
132
|
+
*
|
|
133
|
+
* - `recipientPk` is not the identity point.
|
|
134
|
+
* - `e` is non-zero, a valid Jubjub scalar (`< ℓ`), secret, and fresh per call.
|
|
135
|
+
*
|
|
136
|
+
* @param recipientPk - The recipient's Jubjub public key (`g^ek`).
|
|
137
|
+
* @param value - The value to deliver (any `Uint<128>`; no `2^48` bound).
|
|
138
|
+
* @param e - A fresh ephemeral scalar.
|
|
139
|
+
* @param domain - The consumer's domain-separation tag (must match decrypt).
|
|
140
|
+
* @return The ciphertext `{ ephemeralPk = g^e, ct = value + KDF(pk^e) }`.
|
|
141
|
+
*/
|
|
142
|
+
export pure circuit encrypt(
|
|
143
|
+
recipientPk: JubjubPoint,
|
|
144
|
+
value: Uint<128>,
|
|
145
|
+
e: Field,
|
|
146
|
+
domain: Bytes<32>
|
|
147
|
+
): Ciphertext {
|
|
148
|
+
const identity = ecMulGenerator(0 as Field);
|
|
149
|
+
assert(recipientPk != identity, "EcdhMask: identity pk");
|
|
150
|
+
const ephemeralPk = ecMulGenerator(e);
|
|
151
|
+
// Guard the ephemeral POINT, not the scalar: `ephemeralPk == identity`
|
|
152
|
+
// captures `e == 0` and any `e` that reduces to 0 mod l, so the guard is
|
|
153
|
+
// correct regardless of how the runtime treats an out-of-range scalar. This
|
|
154
|
+
// subsumes `e != 0` (see the weak-inputs note).
|
|
155
|
+
assert(ephemeralPk != identity, "EcdhMask: zero ephemeral");
|
|
156
|
+
const sShared = ecMul(recipientPk, e);
|
|
157
|
+
const mask = kdf(sShared, domain);
|
|
158
|
+
return Ciphertext { ephemeralPk: ephemeralPk, ct: (value as Field) + mask };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @description Recovers the value from a ciphertext using the recipient's
|
|
163
|
+
* secret scalar. Pure and off-chain (no proof needed to read a received value).
|
|
164
|
+
*
|
|
165
|
+
* @param ciphertext - The ciphertext to decrypt.
|
|
166
|
+
* @param ekScalar - The recipient's secret scalar (`crypto/ElGamal`'s
|
|
167
|
+
* `secretToScalar(EK)`).
|
|
168
|
+
* @param domain - The consumer's domain-separation tag (must match encrypt).
|
|
169
|
+
* @return The recovered value as a `Field` (equal to the original `value`,
|
|
170
|
+
* which is `< 2^128`).
|
|
171
|
+
*/
|
|
172
|
+
export pure circuit decrypt(ciphertext: Ciphertext, ekScalar: Field, domain: Bytes<32>): Field {
|
|
173
|
+
const sShared = ecMul(ciphertext.ephemeralPk, ekScalar);
|
|
174
|
+
const mask = kdf(sShared, domain);
|
|
175
|
+
return ciphertext.ct - mask;
|
|
176
|
+
}
|
|
177
|
+
}
|