@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.
Files changed (52) hide show
  1. package/README.md +9 -4
  2. package/access/AccessControl.compact +8 -9
  3. package/access/Ownable.compact +21 -20
  4. package/access/ShieldedAccessControl.compact +20 -19
  5. package/access/ZOwnablePK.compact +5 -3
  6. package/crypto/Ecdh.compact +153 -0
  7. package/crypto/EcdhMask.compact +146 -84
  8. package/crypto/Ecdsa.compact +1 -1
  9. package/crypto/Eip712.compact +1 -1
  10. package/crypto/ElGamal.compact +13 -13
  11. package/math/UintN.compact +82 -0
  12. package/multisig/ForwarderPrivate.compact +6 -17
  13. package/multisig/ForwarderShielded.compact +1 -1
  14. package/multisig/ForwarderUnshielded.compact +1 -1
  15. package/multisig/ProposalManager.compact +127 -65
  16. package/multisig/Secp256k1EcdsaMultisig.compact +1 -1
  17. package/multisig/ShieldedTreasury.compact +8 -20
  18. package/multisig/ShieldedTreasuryStateless.compact +9 -21
  19. package/multisig/Signer.compact +1 -1
  20. package/multisig/UnshieldedTreasury.compact +11 -24
  21. package/multisig/examples/ForwarderPrivateExample.compact +2 -3
  22. package/multisig/examples/ForwarderShieldedExample.compact +1 -1
  23. package/multisig/examples/ForwarderUnshieldedExample.compact +1 -1
  24. package/multisig/examples/{ShieldedMultiSigV2Example.compact → NativeShieldedStatelessTreasuryExample.compact} +23 -23
  25. package/multisig/examples/NativeShieldedTokenIssuerExample.compact +1 -1
  26. package/multisig/presets/NativeShieldedStatelessTreasury.compact +443 -0
  27. package/multisig/presets/NativeShieldedTokenIssuer.compact +2 -5
  28. package/package.json +11 -11
  29. package/security/Allowlist.compact +1 -1
  30. package/security/Blocklist.compact +1 -1
  31. package/security/Initializable.compact +3 -1
  32. package/security/Pausable.compact +1 -1
  33. package/token/ConfidentialFungibleToken.compact +22 -70
  34. package/token/ConfidentialNoteFungibleToken.compact +741 -0
  35. package/token/FungibleToken.compact +36 -30
  36. package/token/MultiToken.compact +27 -21
  37. package/token/NativeShieldedToken.compact +4 -12
  38. package/token/NativeShieldedTokenCore.compact +1 -1
  39. package/token/NativeShieldedTokenFamily.compact +4 -4
  40. package/token/NonFungibleToken.compact +28 -22
  41. package/token/examples/ZOwnableNativeShieldedTokenExample.compact +1 -1
  42. package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
  43. package/token/extensions/ConfidentialNoteFungibleTokenDelivery.compact +274 -0
  44. package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
  45. package/token/extensions/NativeShieldedTokenFamilyPublicSupply.compact +1 -1
  46. package/token/extensions/NativeShieldedTokenPublicSupply.compact +1 -1
  47. package/token/extensions/NativeShieldedTokenPublicSupplyCore.compact +1 -1
  48. package/token/presets/ZOwnableNativeShieldedToken.compact +1 -1
  49. package/utils/EvmAbi.compact +1 -1
  50. package/utils/Utils.compact +1 -1
  51. package/multisig/RecipientTypes.compact +0 -48
  52. package/multisig/presets/ShieldedMultiSigV2.compact +0 -295
@@ -1,23 +1,44 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0 (crypto/EcdhMask.compact)
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. 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.
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
- * 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.
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. 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
- * `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 (ECIES without a MAC) over Jubjub and
66
- * inherits its guarantees: IND-CPA under the Oracle Diffie-Hellman assumption in
67
- * the random-oracle model (the `persistentHash` KDF keyed by the secret point
68
- * `S`). It is NOT IND-CCA: the additive pad is malleable (see Integrity), which
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 carries a large margin: `kdf` returns
71
- * a `degradeToTransient` output in `[0, 2^248)` while `value < 2^128`, so even
72
- * treating the KDF output as uniform the value is hidden with about `2^-120`
73
- * statistical slack, leaving only the assumption that the hash of the DH secret
74
- * is PRF-like. Confidentiality relies on the runtime constraining every
75
- * `JubjubPoint` into the prime-order subgroup (see `crypto/ElGamal`), so `S`
76
- * cannot be a low-order point that leaks `ek`. Binding the ephemeral `E` into the
77
- * KDF is deliberately omitted: for a fixed `recipientPk`, `E = g^e` and
78
- * `S = pk^e` are both determined by `e`, so `KDF(S)` already varies with `E` and
79
- * folding it in buys nothing.
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: the ephemeral public key and
98
- * the field-masked value.
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
- * @description Derives the field mask from the ECDH shared secret point,
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 (any `Uint<128>`; no `2^48` bound).
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<128>,
157
+ value: Uint<248>,
147
158
  e: JubjubScalar,
148
159
  domain: Bytes<32>
149
160
  ): Ciphertext {
150
- const identity = ecMulGenerator(0 as JubjubScalar);
151
- assert(recipientPk != identity, "EcdhMask: identity pk");
152
- const ephemeralPk = ecMulGenerator(e);
153
- // Guard the ephemeral POINT, not the scalar: `ephemeralPk == identity`
154
- // captures `e == 0` and any `e` that reduces to 0 mod l, so the guard is
155
- // correct regardless of how the runtime treats an out-of-range scalar. This
156
- // subsumes `e != 0` (see the weak-inputs note).
157
- assert(ephemeralPk != identity, "EcdhMask: zero ephemeral");
158
- const sShared = ecMul(recipientPk, e);
159
- const mask = kdf(sShared, domain);
160
- return Ciphertext { ephemeralPk: ephemeralPk, ct: (value as Field) + mask };
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
- * Requirements:
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
- * - `ciphertext.ephemeralPk` is not the identity point.
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^128`).
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 = ecMul(ciphertext.ephemeralPk, ekScalar);
245
+ const sShared = Ecdh_recoverShared(ciphertext.ephemeralPk, ekScalar);
184
246
  const mask = kdf(sShared, domain);
185
247
  return ciphertext.ct - mask;
186
248
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0 (crypto/Ecdsa.compact)
2
+ // OpenZeppelin Compact Contracts v0.5.0-alpha.2 (crypto/Ecdsa.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 (crypto/Eip712.compact)
2
+ // OpenZeppelin Compact Contracts v0.5.0-alpha.2 (crypto/Eip712.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 (crypto/ElGamal.compact)
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 asserts `pk != identity`, and every encryption path routes through
75
- * it, so confidentiality is protected regardless of the importer. An importing
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. `encryptPoint` asserts `r != 0` — like `pk = identity`,
81
- * `r = 0` zeros the mask (`r*pk = O`), yielding the non-hiding `(O, m)`; it is
82
- * the unique mask-zeroing randomness, so the two asserts together guarantee a
83
- * non-trivial mask. The remaining obligations the circuit CANNOT enforce:
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
- assert(pk != ecMulGenerator(0 as JubjubScalar), "ElGamal: identity pk");
219
- assert(r != 0 as JubjubScalar, "ElGamal: zero randomness");
220
- const c1 = ecMulGenerator(r);
221
- const mask = ecMul(pk, r);
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.4.0 (multisig/ForwarderPrivate.compact)
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 never appears on the ledger. The parent is deliberately a coin
14
- * public key and not a contract address: a shielded send to a contract
15
- * publishes that contract's address in cleartext, which would defeat the
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, so the ledger does not link them.
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 from anyone who holds no deposited coin (see the
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
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.4.0 (multisig/ForwarderShielded.compact)
2
+ // OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/ForwarderShielded.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 (multisig/ForwarderUnshielded.compact)
2
+ // OpenZeppelin Compact Contracts v0.5.0-alpha.2 (multisig/ForwarderUnshielded.compact)
3
3
 
4
4
  pragma language_version >= 0.26.0;
5
5