Skip to content

Commit f56fe7c

Browse files
authored
feat!: return Option from to_address_point and point_from_x_coord (#8… (#17134)
# Motivation Some AztecAddresses and x coordinates may not correspond to valid points on the curve. Previously, `to_address_point` `point_from_x_coord` and `point_from_x_coord_and_sign` assumed the x coordinate were always valid, which could lead to runtime errors or potential security issues. Now, both functions return an `Option`, returning `None` if the x coordinate does not correspond to a valid point. This makes handling invalid addresses explicit and safe. # Key points - `sqrt` moved from `aztec-nr/aztec/src/utils/field.nr` to `noir-protocol-circuits` - Deleted redundant `field.nr` from `aztec-nr` - `AztecAddress::to_address_point` now returns `Option<AddressPoint>` - `point_from_x_coord` now returns `Option<Point>` - `point_from_x_coord_and_sign` now returns `Option<Point>` as well - Existing internal call sites temporarily use `.unwrap()` to maintain behaviour - Added tests in `point.nr`, `field.nr`, and `aztec_address.nr` to verify Optionals in all relevant functions: - Valid x (sqrt exists) - Invalid x (sqrt fails, returns `None`) # Breaking change Any code calling `to_address_point`, `point_from_x_coord`, or `point_from_x_coord_and_sign` must now handle `Option` instead of assuming a valid point. # Addresses Closes #8970
2 parents 63acec5 + 7b0c0e4 commit f56fe7c

File tree

9 files changed

+230
-246
lines changed

9 files changed

+230
-246
lines changed

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@
323323
"tldr",
324324
"tload",
325325
"tmpfs",
326+
"tonelli",
326327
"toplevel",
327328
"tparam",
328329
"transferables",

noir-projects/aztec-nr/aztec/src/keys/ecdh_shared_secret.nr

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,27 @@ pub fn derive_ecdh_shared_secret(secret: Scalar, public_key: Point) -> Point {
1616
shared_secret
1717
}
1818

19-
/// Computes a standard ecdh shared secret using the address public key of the given address:
20-
/// [ephemeral_secret] * recipient_address_public_key = shared_secret.
21-
/// The intention is that the _creator_ of a shared secret would call this function,
22-
/// given the address of their intended recipient.
19+
/// Computes a standard ECDH shared secret using the public key corresponding to an AztecAddress:
20+
///
21+
/// # Formula
22+
/// `[ephemeral_secret] * recipient_address_public_key = shared_secret`
23+
///
24+
/// # Usage
25+
/// The intention is that the _creator_ of a shared secret calls this function,
26+
/// providing the address of their intended recipient.
27+
///
28+
/// # Note
29+
/// The function returns `Option<Point>` because the recipient address might be invalid
30+
/// (i.e., not correspond to a point on the curve). Callers must handle the `None` case.
31+
/// This is unlike `derive_ecdh_shared_secret`, which always returns a `Point` because it
32+
/// operates on guaranteed-valid inputs.
2333
pub fn derive_ecdh_shared_secret_using_aztec_address(
2434
ephemeral_secret: Scalar,
2535
recipient_address: AztecAddress,
26-
) -> Point {
27-
derive_ecdh_shared_secret(ephemeral_secret, recipient_address.to_address_point().inner)
36+
) -> Option<Point> {
37+
recipient_address.to_address_point().map(|addr_point| {
38+
derive_ecdh_shared_secret(ephemeral_secret, addr_point.inner)
39+
})
2840
}
2941

3042
#[test]
@@ -80,12 +92,12 @@ unconstrained fn test_shared_secret_computation_from_address_in_both_directions(
8092
// If needed, we negate the pk's so that they yield valid address points.
8193
// (We could also have negated the secrets, but there's no negate method for
8294
// EmbeddedCurvesScalar).
83-
pk_a = if (AztecAddress::from_field(pk_a.x).to_address_point().inner == pk_a) {
95+
pk_a = if (AztecAddress::from_field(pk_a.x).to_address_point().unwrap().inner == pk_a) {
8496
pk_a
8597
} else {
8698
pk_a.neg()
8799
};
88-
pk_b = if (address_b.to_address_point().inner == pk_b) {
100+
pk_b = if (address_b.to_address_point().unwrap().inner == pk_b) {
89101
pk_b
90102
} else {
91103
pk_b.neg()
@@ -94,5 +106,5 @@ unconstrained fn test_shared_secret_computation_from_address_in_both_directions(
94106
let shared_secret = derive_ecdh_shared_secret_using_aztec_address(secret_a, address_b);
95107
let shared_secret_alt = derive_ecdh_shared_secret(secret_b, pk_a);
96108

97-
assert_eq(shared_secret, shared_secret_alt);
109+
assert_eq(shared_secret.unwrap(), shared_secret_alt);
98110
}

noir-projects/aztec-nr/aztec/src/messages/encryption/aes128.nr

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,10 @@ impl LogEncryption for AES128 {
173173
let eph_pk_sign_byte: u8 = get_sign_of_point(eph_pk) as u8;
174174

175175
// (not to be confused with the tagging shared secret)
176+
// TODO (#17158): Currently we unwrap the Option returned by derive_ecdh_shared_secret_using_aztec_address.
177+
// We need to handle the case where the ephemeral public key is invalid to prevent potential DoS vectors.
176178
let ciphertext_shared_secret =
177-
derive_ecdh_shared_secret_using_aztec_address(eph_sk, recipient);
179+
derive_ecdh_shared_secret_using_aztec_address(eph_sk, recipient).unwrap();
178180
// TODO: also use this shared secret for deriving note randomness.
179181

180182
// *****************************************************************************
@@ -338,7 +340,8 @@ impl LogEncryption for AES128 {
338340
let eph_pk = point_from_x_coord_and_sign(eph_pk_x, eph_pk_sign_bool);
339341

340342
// Derive shared secret
341-
let ciphertext_shared_secret = get_shared_secret(recipient, eph_pk);
343+
// TODO(#17158): handle invalid ephemeral keys when decrypting to prevent DoS vectors
344+
let ciphertext_shared_secret = get_shared_secret(recipient, eph_pk.unwrap());
342345

343346
// Derive symmetric keys:
344347
let pairs = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<2>(
@@ -426,7 +429,8 @@ mod test {
426429
EmbeddedCurveScalar::from_field(eph_sk),
427430
recipient,
428431
);
429-
let _ = OracleMock::mock("utilityGetSharedSecret").returns(shared_secret);
432+
433+
let _ = OracleMock::mock("utilityGetSharedSecret").returns(shared_secret.unwrap());
430434

431435
// Decrypt the log
432436
let decrypted = AES128::decrypt_log(encrypted_log, recipient);

noir-projects/aztec-nr/aztec/src/oracle/aes128_decrypt.nr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ mod test {
3434
let env = TestEnvironment::new();
3535

3636
env.utility_context(|_| {
37-
let ciphertext_shared_secret = point_from_x_coord(1);
37+
let ciphertext_shared_secret = point_from_x_coord(1).unwrap();
3838

3939
let (sym_key, iv) = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<1>(
4040
ciphertext_shared_secret,
@@ -91,7 +91,7 @@ mod test {
9191
// tuple. The eventual decryptor will expect the mac in the decrypted plaintext
9292
// to match the mac that was broadcast. If not, the recipient knows that
9393
// decryption has failed.
94-
let ciphertext_shared_secret = point_from_x_coord(1);
94+
let ciphertext_shared_secret = point_from_x_coord(1).unwrap();
9595

9696
let (sym_key, iv) = derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2_unsafe::<1>(
9797
ciphertext_shared_secret,

noir-projects/aztec-nr/aztec/src/utils/field.nr

Lines changed: 0 additions & 200 deletions
This file was deleted.

noir-projects/aztec-nr/aztec/src/utils/mod.nr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
pub mod array;
22
pub mod comparison;
33
pub mod conversion;
4-
pub mod field;
54
pub mod point;
65
pub mod random;
76
pub mod to_bytes;

0 commit comments

Comments
 (0)