@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/crypto/EcdhMask.compact
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (crypto/EcdhMask.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module EcdhMask
|
|
8
8
|
* @description Stateless ECDH hybrid encryption of a single field element to a
|
|
9
|
-
* Jubjub public key
|
|
10
|
-
*
|
|
11
|
-
* discrete-log search and the value is
|
|
12
|
-
* direct-decrypt counterpart to the
|
|
13
|
-
*
|
|
9
|
+
* Jubjub public key, built on the key agreement in `crypto/Ecdh`. Delivers a
|
|
10
|
+
* value to whoever holds the secret behind a Jubjub public key WITHOUT encoding
|
|
11
|
+
* it in the exponent, so recovery needs no discrete-log search and the value is
|
|
12
|
+
* unbounded within `Uint<248>`. This is the direct-decrypt counterpart to the
|
|
13
|
+
* homomorphic `crypto/ElGamal`: use ElGamal to accumulate ciphertexts, use this
|
|
14
|
+
* to hand a recipient the plaintext.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* @see https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_6.pdf#page=461 Boneh and Shoup, A Graduate Course in Applied Cryptography v0.6, §11.5 ElGamal encryption (E_EG), Remark 11.1 (E'_EG), Theorem 11.4
|
|
17
|
+
* @see https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_6.pdf#page=495 Boneh and Shoup, §12.4 CCA-secure ElGamal encryption, Theorem 12.4
|
|
18
|
+
* @see https://www.shoup.net/papers/twindh.pdf Cash, Kiltz and Shoup, The Twin Diffie-Hellman Problem and Applications, §1.1 Hashed ElGamal Encryption
|
|
19
|
+
* @see https://cseweb.ucsd.edu/~mihir/papers/dhies.pdf Abdalla, Bellare and Rogaway, DHIES, §8 ODH and SDH
|
|
20
|
+
* @see https://www.secg.org/sec1-v2.pdf#page=58 SEC 1 v2.0 §5.1.3, Encryption Operation
|
|
21
|
+
* @see https://www.secg.org/sec1-v2.pdf#page=59 SEC 1 v2.0 §5.1.4, Decryption Operation, action 3
|
|
22
|
+
*
|
|
23
|
+
* @dev Construction and notation. Boneh and Shoup's E'_EG symbol follows in
|
|
24
|
+
* brackets (§11.5, Remark 11.1).
|
|
25
|
+
* - `pk = g^ek`: the recipient's public key [`u = g^α`].
|
|
26
|
+
* - `e`: the ephemeral scalar, fresh per call [`β`].
|
|
27
|
+
* - `E = g^e`: the ephemeral public key [`v = g^β`].
|
|
28
|
+
* - `S = pk^e = g^{ek*e}`: the shared point, from `crypto/Ecdh` [`w = u^β`].
|
|
29
|
+
* - `mask = KDF(S, domain)`: the domain-separated field mask, see `kdf`
|
|
30
|
+
* [`k = H(w)`]. E_EG hashes `(v, w)`; E'_EG hashes `w` alone, as here.
|
|
31
|
+
* `kdf` is `kdfFromDigest(pointDigest(S), domain)`, a one-step hash KDF
|
|
32
|
+
* (the `H(Z ‖ FixedInfo)` shape of NIST SP 800-56C §4, one block, 248-bit
|
|
33
|
+
* output); a consumer deriving several masks from one `S` digests it once.
|
|
34
|
+
* - `ct = value + mask`: the field one-time pad [`c = E_s(k, m)`].
|
|
35
|
+
* - `Ciphertext { ephemeralPk: E, ct }`: the ciphertext [`(v, c)`].
|
|
36
|
+
* - Recipient recovers `S = E^ek`, `mask = KDF(S, domain)`, `value = ct - mask`.
|
|
37
|
+
*
|
|
38
|
+
* @dev Key agreement. `deriveShared`, `recoverShared` and the `SharedSecret`
|
|
39
|
+
* struct live in `crypto/Ecdh` and are not re-exported here; a consumer that
|
|
40
|
+
* needs them imports that module directly. It owns the weak-input guards every
|
|
41
|
+
* sender path here inherits.
|
|
21
42
|
*
|
|
22
43
|
* @dev Freshness and secrecy precondition (CRITICAL). `e` MUST be both unique per
|
|
23
44
|
* call to the same recipient AND secret (unpredictable to observers), even across
|
|
@@ -25,7 +46,8 @@ pragma language_version >= 0.26.0;
|
|
|
25
46
|
*
|
|
26
47
|
* Uniqueness: the mask is a one-time pad, so a repeated `e` repeats `S` and
|
|
27
48
|
* `mask`, and reusing a pad is catastrophic (subtracting two ciphertexts recovers
|
|
28
|
-
* both values).
|
|
49
|
+
* both values). Padding two values under the same `(S, domain)` reuses the pad
|
|
50
|
+
* the same way (see `kdf`).
|
|
29
51
|
*
|
|
30
52
|
* Secrecy: `recipientPk` is public, so anyone who learns `e` recomputes
|
|
31
53
|
* `S = recipientPk^e`, hence the mask, and opens the ciphertext. A unique but
|
|
@@ -38,21 +60,11 @@ pragma language_version >= 0.26.0;
|
|
|
38
60
|
* This is stronger than exponential ElGamal's uniqueness requirement (there,
|
|
39
61
|
* reuse merely leaks plaintext equality); do not treat `e` as "any randomness."
|
|
40
62
|
*
|
|
41
|
-
* @dev Weak inputs.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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
|
-
* `decrypt` guards the same point, refusing an identity ephemeral rather than
|
|
53
|
-
* unmasking against that public constant.
|
|
54
|
-
* These are the only weak inputs (for a subgroup key, `S = identity` iff the
|
|
55
|
-
* ephemeral point is the identity).
|
|
63
|
+
* @dev Weak inputs. An identity `recipientPk` or a zero `e` collapses `S` to the
|
|
64
|
+
* identity point, giving `mask = KDF(identity, domain)`, a value any observer can
|
|
65
|
+
* recompute, so `ct = value + mask` opens trivially. `crypto/Ecdh.deriveShared`
|
|
66
|
+
* rejects both, and `encrypt` reaches them only through it; that module's
|
|
67
|
+
* weak-inputs note carries the full argument.
|
|
56
68
|
*
|
|
57
69
|
* @dev Integrity. The pad is malleable and NO authentication is added here. This
|
|
58
70
|
* is safe in the intended on-chain use, where the ciphertext is computed
|
|
@@ -62,21 +74,29 @@ pragma language_version >= 0.26.0;
|
|
|
62
74
|
* enable theft. Consumers with a different threat model (mutable storage,
|
|
63
75
|
* in-transit tampering) MUST add their own authentication.
|
|
64
76
|
*
|
|
65
|
-
* @dev Security. This is hashed ElGamal
|
|
66
|
-
*
|
|
67
|
-
* the
|
|
68
|
-
*
|
|
77
|
+
* @dev Security. This is hashed ElGamal over Jubjub: Boneh and Shoup, *A
|
|
78
|
+
* Graduate Course in Applied Cryptography* v0.6, §11.5, in the Remark 11.1
|
|
79
|
+
* form that keys the KDF on the shared point alone. For plaintexts well below
|
|
80
|
+
* the pad's width (see the hiding bound below) it inherits that scheme's
|
|
81
|
+
* guarantee: IND-CPA under CDH in the random-oracle model (Theorem 11.4, the
|
|
82
|
+
* `persistentHash` KDF keyed by the secret point `S`), and so also under the
|
|
83
|
+
* stronger Oracle Diffie-Hellman assumption. It is NOT IND-CCA: Theorem 12.4
|
|
84
|
+
* needs a MAC, and the additive pad is malleable (see Integrity), which
|
|
69
85
|
* is acceptable here only because the on-chain ciphertext is bound in-circuit to
|
|
70
|
-
* the value it commits to elsewhere. Hiding
|
|
71
|
-
* a `degradeToTransient`
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* is PRF-like.
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
86
|
+
* the value it commits to elsewhere. Hiding depends on the plaintext width. The
|
|
87
|
+
* `kdf` output is a `degradeToTransient` value in `[0, 2^248)`. Taken as
|
|
88
|
+
* uniform, it hides a value below `2^w` within `2^(w-248)` statistical slack. A
|
|
89
|
+
* `Uint<128>` keeps about `2^-120`, leaving only the assumption that the hash of
|
|
90
|
+
* the DH secret is PRF-like. A full `Uint<248>` has no margin: a random one
|
|
91
|
+
* loses under one bit on average, but two chosen values far apart are
|
|
92
|
+
* distinguishable. The type excludes anything at or above `2^248`.
|
|
93
|
+
* Confidentiality relies on the runtime constraining every `JubjubPoint` into
|
|
94
|
+
* the prime-order subgroup (see `crypto/ElGamal`), so `S` cannot be a
|
|
95
|
+
* low-order point that leaks `ek`.
|
|
96
|
+
* Binding the ephemeral `E` into the KDF is deliberately omitted (Remark 11.1):
|
|
97
|
+
* for a fixed `recipientPk`, `E = g^e` and `S = pk^e` are both determined by
|
|
98
|
+
* `e`, so `KDF(S)` already varies with `E`; folding it in only tightens the CCA
|
|
99
|
+
* reduction (Remark 12.2) which this module does not claim.
|
|
80
100
|
*
|
|
81
101
|
* @dev Shared key with `crypto/ElGamal` (in scope). A consumer MAY use the same
|
|
82
102
|
* recipient Jubjub key for both these memos (hashed ElGamal) and exponential-
|
|
@@ -84,6 +104,9 @@ pragma language_version >= 0.26.0;
|
|
|
84
104
|
* safe: the two schemes share only the public key, this KDF is domain-separated
|
|
85
105
|
* and keyed by the secret DH point, and neither exposes a decryption oracle, so
|
|
86
106
|
* under CPA / ODH a ciphertext of one scheme does not help attack the other.
|
|
107
|
+
* The per-encryption scalars MUST be independent (e.g. distinct
|
|
108
|
+
* `expandRandomness` tags over one seed): a shared scalar shares `S`, so the
|
|
109
|
+
* memo becomes a plaintext-check oracle for the ElGamal ciphertext.
|
|
87
110
|
*
|
|
88
111
|
* @dev No ledger state, no witnesses. Every circuit is pure and takes its keys,
|
|
89
112
|
* value, and randomness as explicit arguments. `decrypt` is exposed as a pure
|
|
@@ -92,37 +115,22 @@ pragma language_version >= 0.26.0;
|
|
|
92
115
|
*/
|
|
93
116
|
module EcdhMask {
|
|
94
117
|
import CompactStandardLibrary;
|
|
118
|
+
import "./Ecdh" prefix Ecdh_;
|
|
95
119
|
|
|
96
120
|
/**
|
|
97
|
-
* @description An ECDH one-time-pad ciphertext
|
|
98
|
-
*
|
|
121
|
+
* @description An ECDH one-time-pad ciphertext, `(v, c)` in Boneh and Shoup.
|
|
122
|
+
* Public once written; neither field is secret on its own.
|
|
123
|
+
*
|
|
124
|
+
* @notice `ephemeralPk` is never the identity for a ciphertext `encrypt`
|
|
125
|
+
* produced, and `decrypt` rejects one that is. A consumer that hand-builds a
|
|
126
|
+
* placeholder with an identity `ephemeralPk` must document it as a sentinel.
|
|
99
127
|
*/
|
|
100
128
|
export struct Ciphertext {
|
|
129
|
+
/** `E = g^e`, the sender's ephemeral public key. Never the identity. */
|
|
101
130
|
ephemeralPk: JubjubPoint;
|
|
102
|
-
ct: Field;
|
|
103
|
-
}
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
* domain-separated by a caller-supplied `domain` tag. Hashes the point to
|
|
108
|
-
* `Bytes<32>`, then re-hashes it with the domain tag and truncates into the
|
|
109
|
-
* field via `degradeToTransient`. The consumer chooses `domain` (so this
|
|
110
|
-
* module is not tied to any one protocol) and MUST use the same value on
|
|
111
|
-
* encrypt and decrypt.
|
|
112
|
-
*
|
|
113
|
-
* @dev Uses `persistentHash` deliberately: the recipient reproduces this mask
|
|
114
|
-
* to decrypt, possibly across a platform upgrade, so it must be upgrade-stable.
|
|
115
|
-
*
|
|
116
|
-
* @param sShared - The ECDH shared secret point (`pk^e` for the sender,
|
|
117
|
-
* `E^ek` for the recipient; equal by construction).
|
|
118
|
-
* @param domain - The consumer's domain-separation tag.
|
|
119
|
-
* @return The field mask.
|
|
120
|
-
*/
|
|
121
|
-
export pure circuit kdf(sShared: JubjubPoint, domain: Bytes<32>): Field {
|
|
122
|
-
const pointHash = persistentHash<JubjubPoint>(sShared);
|
|
123
|
-
return degradeToTransient(
|
|
124
|
-
persistentHash<Vector<2, Bytes<32>>>([pointHash, domain])
|
|
125
|
-
);
|
|
132
|
+
/** `value + KDF(S, domain)`, the field one-time pad over the value. */
|
|
133
|
+
ct: Field;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
/**
|
|
@@ -130,57 +138,111 @@ module EcdhMask {
|
|
|
130
138
|
*
|
|
131
139
|
* @notice `e` MUST be fresh per call (see the module freshness precondition).
|
|
132
140
|
*
|
|
141
|
+
* @constraints k=14, rows=9318 (zkir-v3)
|
|
142
|
+
*
|
|
133
143
|
* Requirements:
|
|
134
144
|
*
|
|
135
145
|
* - `recipientPk` is not the identity point.
|
|
136
146
|
* - `e` is non-zero, a valid Jubjub scalar (`< ℓ`), secret, and fresh per call.
|
|
137
147
|
*
|
|
138
148
|
* @param recipientPk - The recipient's Jubjub public key (`g^ek`).
|
|
139
|
-
* @param value - The value to deliver
|
|
149
|
+
* @param value - The value to deliver, any `Uint<248>`. The hiding margin
|
|
150
|
+
* shrinks as its width grows (see the module security note).
|
|
140
151
|
* @param e - A fresh ephemeral scalar.
|
|
141
152
|
* @param domain - The consumer's domain-separation tag (must match decrypt).
|
|
142
153
|
* @return The ciphertext `{ ephemeralPk = g^e, ct = value + KDF(pk^e) }`.
|
|
143
154
|
*/
|
|
144
155
|
export pure circuit encrypt(
|
|
145
156
|
recipientPk: JubjubPoint,
|
|
146
|
-
value: Uint<
|
|
157
|
+
value: Uint<248>,
|
|
147
158
|
e: JubjubScalar,
|
|
148
159
|
domain: Bytes<32>
|
|
149
160
|
): Ciphertext {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
// The weak-input guards come from `deriveShared`, not repeated here.
|
|
162
|
+
const shared = Ecdh_deriveShared(recipientPk, e);
|
|
163
|
+
const mask = kdf(shared.sShared, domain);
|
|
164
|
+
return Ciphertext { ephemeralPk: shared.ephemeralPk, ct: (value as Field) + mask };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @description Derives the field mask from the ECDH shared secret point and
|
|
169
|
+
* a caller-supplied `domain` tag: hashes the point to `Bytes<32>`, re-hashes
|
|
170
|
+
* it with the tag, and truncates into the field via `degradeToTransient`.
|
|
171
|
+
* The mask is a pure function of `(sShared, domain)`, so the pair MUST match
|
|
172
|
+
* on encrypt and decrypt, and MUST be distinct for every value it pads. Two
|
|
173
|
+
* values padded under one pair share the pad and leak their difference.
|
|
174
|
+
*
|
|
175
|
+
* @dev Uses `persistentHash` deliberately: the recipient reproduces this mask
|
|
176
|
+
* to decrypt, possibly across a platform upgrade, so it must be upgrade-stable.
|
|
177
|
+
*
|
|
178
|
+
* @constraints k=13, rows=7914 (zkir-v3)
|
|
179
|
+
*
|
|
180
|
+
* @param sShared - The ECDH shared secret point (`pk^e` for the sender,
|
|
181
|
+
* `E^ek` for the recipient; equal by construction).
|
|
182
|
+
* @param domain - The consumer's domain-separation tag.
|
|
183
|
+
* @return The field mask.
|
|
184
|
+
*/
|
|
185
|
+
export pure circuit kdf(sShared: JubjubPoint, domain: Bytes<32>): Field {
|
|
186
|
+
return kdfFromDigest(pointDigest(sShared), domain);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @description `H(sShared)`, the secret input of `kdf`. A consumer deriving
|
|
191
|
+
* several masks from one shared point digests it once and calls
|
|
192
|
+
* `kdfFromDigest` per tag.
|
|
193
|
+
*
|
|
194
|
+
* @constraints k=13, rows=4098 (zkir-v3)
|
|
195
|
+
*
|
|
196
|
+
* @param sShared - The ECDH shared secret point.
|
|
197
|
+
* @return The point's digest, secret while `sShared` is.
|
|
198
|
+
*/
|
|
199
|
+
export pure circuit pointDigest(sShared: JubjubPoint): Bytes<32> {
|
|
200
|
+
return persistentHash<JubjubPoint>(sShared);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @description The mask for `domain` from a point digest:
|
|
205
|
+
* `kdf(S, domain) == kdfFromDigest(pointDigest(S), domain)`.
|
|
206
|
+
*
|
|
207
|
+
* @notice One mask per `(digest, domain)`. Pad one value under each pair,
|
|
208
|
+
* and give every mask drawn from one digest its own tag; two values under
|
|
209
|
+
* one pair share the pad and leak their difference.
|
|
210
|
+
*
|
|
211
|
+
* @constraints k=13, rows=3956 (zkir-v3)
|
|
212
|
+
*
|
|
213
|
+
* @param digest - `pointDigest` of the shared point.
|
|
214
|
+
* @param domain - The consumer's domain-separation tag.
|
|
215
|
+
* @return The field mask.
|
|
216
|
+
*/
|
|
217
|
+
export pure circuit kdfFromDigest(digest: Bytes<32>, domain: Bytes<32>): Field {
|
|
218
|
+
return degradeToTransient(
|
|
219
|
+
persistentHash<Vector<2, Bytes<32>>>([digest, domain])
|
|
220
|
+
);
|
|
161
221
|
}
|
|
162
222
|
|
|
163
223
|
/**
|
|
164
224
|
* @description Recovers the value from a ciphertext using the recipient's
|
|
165
225
|
* secret scalar. Pure and off-chain (no proof needed to read a received value).
|
|
166
226
|
*
|
|
167
|
-
*
|
|
227
|
+
* @notice Rejects a ciphertext whose `ephemeralPk` is the identity point
|
|
228
|
+
* (SEC 1 §5.1.4, action 3). `encrypt` cannot produce one, so a wallet that
|
|
229
|
+
* meets one holds a placeholder, not an encryption.
|
|
168
230
|
*
|
|
169
|
-
*
|
|
231
|
+
* @constraints k=14, rows=8976 (zkir-v3)
|
|
170
232
|
*
|
|
171
233
|
* @param ciphertext - The ciphertext to decrypt.
|
|
172
234
|
* @param ekScalar - The recipient's secret scalar (`crypto/ElGamal`'s
|
|
173
235
|
* `secretToScalar(EK)`).
|
|
174
236
|
* @param domain - The consumer's domain-separation tag (must match encrypt).
|
|
175
237
|
* @return The recovered value as a `Field` (equal to the original `value`,
|
|
176
|
-
* which is `< 2^
|
|
238
|
+
* which is `< 2^248`).
|
|
177
239
|
*/
|
|
178
240
|
export pure circuit decrypt(ciphertext: Ciphertext, ekScalar: JubjubScalar, domain: Bytes<32>): Field {
|
|
179
241
|
// `encrypt` never emits an identity ephemeral; one here is a malformed
|
|
180
242
|
// ciphertext whose mask is a public constant, so refuse rather than decrypt.
|
|
181
243
|
assert(ciphertext.ephemeralPk != ecMulGenerator(0 as JubjubScalar),
|
|
182
244
|
"EcdhMask: identity ephemeral");
|
|
183
|
-
const sShared =
|
|
245
|
+
const sShared = Ecdh_recoverShared(ciphertext.ephemeralPk, ekScalar);
|
|
184
246
|
const mask = kdf(sShared, domain);
|
|
185
247
|
return ciphertext.ct - mask;
|
|
186
248
|
}
|
package/crypto/Ecdsa.compact
CHANGED
package/crypto/Eip712.compact
CHANGED
package/crypto/ElGamal.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 (crypto/ElGamal.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -71,16 +71,17 @@ pragma language_version >= 0.26.0;
|
|
|
71
71
|
* @dev Weak keys. Encrypting under the identity point yields `c2 = m` with no
|
|
72
72
|
* masking (the plaintext is exposed), and the identity is a valid subgroup
|
|
73
73
|
* point so the runtime subgroup guarantee does NOT catch it. `encryptPoint`
|
|
74
|
-
* therefore
|
|
75
|
-
*
|
|
74
|
+
* therefore takes its mask from `crypto/Ecdh`'s `deriveShared`, which rejects
|
|
75
|
+
* an identity `pk`, and every encryption path routes through it, so
|
|
76
|
+
* confidentiality is protected regardless of the importer. An importing
|
|
76
77
|
* module MAY additionally reject an identity `pk` where keys enter the system
|
|
77
78
|
* (e.g. at registration) to fail fast with a clear error rather than at first
|
|
78
79
|
* encryption — a UX nicety, not a confidentiality requirement.
|
|
79
80
|
*
|
|
80
|
-
* @dev Randomness. `
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
81
|
+
* @dev Randomness. `r = 0` zeros the mask (`r*pk = O`), yielding the
|
|
82
|
+
* non-hiding `(O, m)`; `deriveShared` rejects it as a zero ephemeral, so
|
|
83
|
+
* together with the identity-`pk` guard the mask is always non-trivial. The
|
|
84
|
+
* remaining obligations the circuit CANNOT enforce:
|
|
84
85
|
* every `r` MUST be a valid Jubjub scalar (`< ℓ`, the subgroup order; larger
|
|
85
86
|
* values fault `ecMul`), and MUST be fresh and secret. Reusing an `r` under the
|
|
86
87
|
* same `pk` leaks the difference of the two plaintexts (`c1` collides, so
|
|
@@ -89,6 +90,7 @@ pragma language_version >= 0.26.0;
|
|
|
89
90
|
*/
|
|
90
91
|
module ElGamal {
|
|
91
92
|
import CompactStandardLibrary;
|
|
93
|
+
import "./Ecdh" prefix Ecdh_;
|
|
92
94
|
|
|
93
95
|
// ---------------------------------------------------------------------------
|
|
94
96
|
// Types
|
|
@@ -215,12 +217,10 @@ module ElGamal {
|
|
|
215
217
|
* @return The ciphertext `(g^r, pk^r * m)`.
|
|
216
218
|
*/
|
|
217
219
|
export pure circuit encryptPoint(pk: JubjubPoint, m: JubjubPoint, r: JubjubScalar): Ciphertext {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
const c2 = ecAdd(mask, m);
|
|
223
|
-
return Ciphertext { c1: c1, c2: c2 };
|
|
220
|
+
// The weak-input guards come from `deriveShared`, not repeated here.
|
|
221
|
+
const shared = Ecdh_deriveShared(pk, r);
|
|
222
|
+
const c2 = ecAdd(shared.sShared, m);
|
|
223
|
+
return Ciphertext { c1: shared.ephemeralPk, c2: c2 };
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (math/UintN.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.26.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module UintN
|
|
8
|
+
* @description Wrapping arithmetic on `Uint<N>` for any width `N`: results
|
|
9
|
+
* are reduced modulo `2^N`, and no input fails. Instantiate with a width, e.g.
|
|
10
|
+
* `UintN<64>`, `UintN<248>`.
|
|
11
|
+
*
|
|
12
|
+
* @dev Names and semantics follow Rust's `wrapping_add` and `wrapping_sub`.
|
|
13
|
+
* The `Field` shape follows Noir's `WrappingAdd` and `WrappingSub`, with
|
|
14
|
+
* arkworks' `UInt<N>` as the generic-width precedent. Noir's `as` truncates,
|
|
15
|
+
* but `as Uint<N>` fails out of range. Each circuit therefore reduces
|
|
16
|
+
* explicitly, selects between two `Field` arms, and casts once, outside the
|
|
17
|
+
* select.
|
|
18
|
+
*
|
|
19
|
+
* @dev The `Field` arithmetic is exact while `a + b` stays below the field
|
|
20
|
+
* modulus, which holds for `N <= 253` and so for every width `Uint<N>` admits.
|
|
21
|
+
*
|
|
22
|
+
* Supported Operations:
|
|
23
|
+
* - MODULUS(): `2^N` as a `Field`.
|
|
24
|
+
* - wrappingAdd(a, b): `(a + b) mod 2^N`.
|
|
25
|
+
* - wrappingSub(a, b): `(a - b) mod 2^N`.
|
|
26
|
+
*/
|
|
27
|
+
module UintN<#N> {
|
|
28
|
+
/**
|
|
29
|
+
* @description `2^N` as a `Field`: the modulus of every circuit in this
|
|
30
|
+
* module.
|
|
31
|
+
*
|
|
32
|
+
* @constraints k=5, rows=23 (zkir-v2, any N)
|
|
33
|
+
* @constraints k=6, rows=31 (zkir-v3, any N)
|
|
34
|
+
*
|
|
35
|
+
* @returns {Field} `2^N`.
|
|
36
|
+
*/
|
|
37
|
+
export pure circuit MODULUS(): Field {
|
|
38
|
+
return fold((acc: Field, _bit: Boolean): Field => acc * 2,
|
|
39
|
+
1 as Field,
|
|
40
|
+
default<Vector<N, Boolean>>);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @description Wrapping (modular) addition. Computes `a + b`, wrapping
|
|
45
|
+
* around at the boundary of the type.
|
|
46
|
+
*
|
|
47
|
+
* @dev The wrap test is `a > MAX - b`, which needs no `Uint<N + 1>`.
|
|
48
|
+
*
|
|
49
|
+
* @constraints depends on N:
|
|
50
|
+
* - UintN<8>: k=9, rows=98 (zkir-v2), k=9, rows=85 (zkir-v3)
|
|
51
|
+
* - UintN<64>: k=9, rows=308 (zkir-v2), k=9, rows=133 (zkir-v3)
|
|
52
|
+
* - UintN<248>: k=10, rows=980 (zkir-v2), k=9, rows=307 (zkir-v3)
|
|
53
|
+
*
|
|
54
|
+
* @param {Uint<N>} a - The first operand.
|
|
55
|
+
* @param {Uint<N>} b - The second operand.
|
|
56
|
+
* @returns {Uint<N>} `(a + b) mod 2^N`.
|
|
57
|
+
*/
|
|
58
|
+
export pure circuit wrappingAdd(a: Uint<N>, b: Uint<N>): Uint<N> {
|
|
59
|
+
const modulus = MODULUS();
|
|
60
|
+
const wraps = a > ((modulus - 1) as Uint<N>) - b;
|
|
61
|
+
const sum = (a as Field) + (b as Field);
|
|
62
|
+
return (wraps ? sum - modulus : sum) as Uint<N>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @description Wrapping (modular) subtraction. Computes `a - b`, wrapping
|
|
67
|
+
* around at the boundary of the type.
|
|
68
|
+
*
|
|
69
|
+
* @constraints depends on N:
|
|
70
|
+
* - UintN<8>: k=9, rows=84 (zkir-v2), k=9, rows=73 (zkir-v3)
|
|
71
|
+
* - UintN<64>: k=9, rows=273 (zkir-v2), k=9, rows=118 (zkir-v3)
|
|
72
|
+
* - UintN<248>: k=10, rows=885 (zkir-v2), k=9, rows=274 (zkir-v3)
|
|
73
|
+
*
|
|
74
|
+
* @param {Uint<N>} a - The minuend.
|
|
75
|
+
* @param {Uint<N>} b - The subtrahend.
|
|
76
|
+
* @returns {Uint<N>} `(a - b) mod 2^N`.
|
|
77
|
+
*/
|
|
78
|
+
export pure circuit wrappingSub(a: Uint<N>, b: Uint<N>): Uint<N> {
|
|
79
|
+
const diff = (a as Field) - (b as Field);
|
|
80
|
+
return (a < b ? diff + MODULUS() : diff) as Uint<N>;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/ForwarderPrivate.compact)
|
|
3
3
|
|
|
4
4
|
pragma language_version >= 0.26.0;
|
|
5
5
|
|
|
@@ -10,25 +10,15 @@ pragma language_version >= 0.26.0;
|
|
|
10
10
|
* Deposits accumulate at the contract (no atomic forward); the operator
|
|
11
11
|
* drains coins by presenting the `(parent, opSecret)` preimage at drain
|
|
12
12
|
* time. The drained coin is sent as a shielded note to the parent key, so
|
|
13
|
-
* the parent
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* guarantee.
|
|
17
|
-
*
|
|
18
|
-
* @notice The commitment hides the parent from the public, not from a
|
|
19
|
-
* depositor. A contract-owned coin's nullifier is a hash of the coin and the
|
|
20
|
-
* contract address, and `sendShielded` derives the nonces of the sent coin
|
|
21
|
-
* and of the change from a public hash of the spent coin's nonce. Whoever
|
|
22
|
-
* knows a deposited coin can therefore find the drain that spends it, recover
|
|
23
|
-
* the drained amount (on a partial drain the change commitment has the value
|
|
24
|
-
* as its only unknown), and test candidate parent keys against the sent
|
|
25
|
-
* coin's commitment. A depositor who can guess the parent can confirm it.
|
|
13
|
+
* the parent stays hidden on-chain. The parent is deliberately a coin public
|
|
14
|
+
* key and not a contract address: a shielded send to a contract publishes
|
|
15
|
+
* that contract's address in cleartext, which would defeat the guarantee.
|
|
26
16
|
*
|
|
27
17
|
* Knowledge of the preimage is the sole authorization gate — there is
|
|
28
18
|
* no signer set and no nullifier scheme. The operational secret is
|
|
29
19
|
* held off-chain; the contract never sees it except during a drain.
|
|
30
20
|
* Two forwarders bound to the same parent with different operational
|
|
31
|
-
* secrets produce different commitments
|
|
21
|
+
* secrets produce different commitments and are unlinkable on-chain.
|
|
32
22
|
*
|
|
33
23
|
* Underscore-prefixed circuits have no access control beyond the
|
|
34
24
|
* preimage check inside `_drain`. State access is gated by the
|
|
@@ -113,8 +103,7 @@ module ForwarderPrivate {
|
|
|
113
103
|
* @description Spends a previously-deposited shielded coin to the parent,
|
|
114
104
|
* a coin public key. The caller proves knowledge of `(parent, opSecret)`
|
|
115
105
|
* matching the stored commitment; the coin is sent as a plain shielded note,
|
|
116
|
-
* so the parent stays hidden
|
|
117
|
-
* module notice). If `coin.value` exceeds `value`,
|
|
106
|
+
* so the parent stays hidden on-chain. If `coin.value` exceeds `value`,
|
|
118
107
|
* `sendShielded` routes the change back to this contract as a self-owned
|
|
119
108
|
* output; `_drain` returns it untouched via `result.change`. The
|
|
120
109
|
* implementing contract chooses what to do with it: leave it dwelling at
|