Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add check for canonical point encodings where needed, and tests #193

Merged
merged 6 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frost-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ proptest-derive = { version = "0.3", optional = true }
serde_json = { version = "1.0", optional = true }

[dev-dependencies]
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
curve25519-dalek = { version = "=4.0.0-pre.1", features = ["serde"] }
lazy_static = "1.4"
proptest = "1.0"
rand = "0.8"
Expand Down
3 changes: 2 additions & 1 deletion frost-ed25519/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ description = "A Schnorr signature scheme over the prime-order Ristretto group t
features = ["nightly"]

[dependencies]
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
curve25519-dalek = { version = "=4.0.0-pre.1", features = ["serde"] }
frost-core = { path = "../frost-core", features = ["test-impl"] }
frost-ristretto255 = { path = "../frost-ristretto255" }
rand_core = "0.6"
Expand All @@ -33,6 +33,7 @@ bincode = "1"
criterion = "0.4"
ed25519-dalek = "1.0.1"
ed25519-zebra = "3.0.0"
hex = "0.4.3"
lazy_static = "1.4"
proptest = "1.0"
proptest-derive = "0.3"
Expand Down
9 changes: 9 additions & 0 deletions frost-ed25519/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ impl Group for Ed25519Group {
if point == Self::identity() {
Err(GroupError::InvalidIdentityElement)
} else if point.is_torsion_free() {
// At this point we should reject points which were not
// encoded canonically (i.e. Y coordinate >= p).
// However, we don't allow non-prime order elements,
// and that suffices to also reject non-canonical encodings
// per https://eprint.iacr.org/2020/1244.pdf:
//
// > There are 19 elliptic curve points that can be encoded in a non-canonical form.
// > (...) Among these points there are 2 points of small order and from the
// > remaining 17 y-coordinates only 10 decode to valid curve points all of mixed order.
Ok(point)
} else {
Err(GroupError::InvalidNonPrimeOrderElement)
Expand Down
13 changes: 12 additions & 1 deletion frost-ed25519/tests/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ fn check_bad_batch_verify() {
fn check_deserialize_identity() {
let encoded_identity = EdwardsPoint::identity().compress().to_bytes();

let r = <<Ed25519Sha512 as Ciphersuite>::Group as Group>::deserialize(&encoded_identity);
let r = <Ed25519Sha512 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::InvalidIdentityElement));
}

#[test]
fn check_deserialize_non_prime_order() {
let encoded_point =
hex::decode("0300000000000000000000000000000000000000000000000000000000000000")
.unwrap()
.try_into()
.unwrap();
let r = <Ed25519Sha512 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::InvalidNonPrimeOrderElement));
}
1 change: 1 addition & 0 deletions frost-ed448/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sha3 = "0.10.6"
bincode = "1"
criterion = "0.4"
lazy_static = "1.4"
hex = "0.4.3"
proptest = "1.0"
proptest-derive = "0.3"
rand = "0.8"
Expand Down
11 changes: 9 additions & 2 deletions frost-ed448/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,19 @@ impl Group for Ed448Group {
}

fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError> {
match CompressedEdwardsY(*buf).decompress() {
let compressed = CompressedEdwardsY(*buf);
match compressed.decompress() {
Some(point) => {
if point == Self::identity() {
Err(GroupError::InvalidIdentityElement)
} else if point.is_torsion_free() {
Ok(point)
// decompress() does not check for canonicality, so we
// check by recompressing and comparing
if point.compress().0 != compressed.0 {
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
Err(GroupError::MalformedElement)
} else {
Ok(point)
}
} else {
Err(GroupError::InvalidNonPrimeOrderElement)
}
Expand Down
33 changes: 32 additions & 1 deletion frost-ed448/tests/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,37 @@ fn check_bad_batch_verify() {
fn check_deserialize_identity() {
let encoded_identity = ExtendedPoint::identity().compress().0;

let r = <<Ed448Shake256 as Ciphersuite>::Group as Group>::deserialize(&encoded_identity);
let r = <Ed448Shake256 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::InvalidIdentityElement));
}

#[test]
fn check_deserialize_non_canonical() {
let mut encoded_generator = ExtendedPoint::generator().compress().0;

let r = <Ed448Shake256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert!(r.is_ok());

// The last byte only should have the sign bit. Set all other bits to
// create a non-canonical encoding.
encoded_generator[56] |= 0x7f;
let r = <Ed448Shake256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert_eq!(r, Err(GroupError::MalformedElement));

// Besides the last byte, it is still possible to get non-canonical encodings.
// This is y = p + 19 which is non-canonical and maps to a valid prime-order point.
let encoded_point = hex::decode("12000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00").unwrap().try_into().unwrap();
let r = <Ed448Shake256 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::MalformedElement));
}

#[test]
fn check_deserialize_non_prime_order() {
let encoded_point =
hex::decode("030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
.unwrap()
.try_into()
.unwrap();
let r = <Ed448Shake256 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::InvalidNonPrimeOrderElement));
}
1 change: 1 addition & 0 deletions frost-p256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ bincode = "1"
criterion = "0.4"
ed25519-dalek = "1.0.1"
ed25519-zebra = "3.0.0"
hex = "0.4.3"
lazy_static = "1.4"
proptest = "1.0"
proptest-derive = "0.3"
Expand Down
28 changes: 27 additions & 1 deletion frost-p256/tests/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,32 @@ fn check_deserialize_identity() {
// allow us to change that. Try to send something similar.
let encoded_identity = [0u8; 33];

let r = <<P256Sha256 as Ciphersuite>::Group as Group>::deserialize(&encoded_identity);
let r = <P256Sha256 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::MalformedElement));
}

#[test]
fn check_deserialize_non_canonical() {
let mut encoded_generator = <P256Sha256 as Ciphersuite>::Group::serialize(
&<P256Sha256 as Ciphersuite>::Group::generator(),
);

let r = <P256Sha256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert!(r.is_ok());

// The first byte should be 0x02 or 0x03. Set other value to
// create a non-canonical encoding.
encoded_generator[0] = 0xFF;
let r = <P256Sha256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert_eq!(r, Err(GroupError::MalformedElement));

// Besides the first byte, it is still possible to get non-canonical encodings.
// This is x = p + 5 which is non-canonical and maps to a valid prime-order point.
let encoded_point =
hex::decode("02ffffffff00000001000000000000000000000001000000000000000000000004")
.unwrap()
.try_into()
.unwrap();
let r = <P256Sha256 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::MalformedElement));
}
2 changes: 1 addition & 1 deletion frost-ristretto255/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ description = "A Schnorr signature scheme over the prime-order Ristretto group t
features = ["nightly"]

[dependencies]
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
curve25519-dalek = { version = "=4.0.0-pre.1", features = ["serde"] }
frost-core = { path = "../frost-core", features = ["test-impl"] }
rand_core = "0.6"
sha2 = "0.10.2"
Expand Down
2 changes: 1 addition & 1 deletion frost-ristretto255/tests/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ fn check_bad_batch_verify() {
fn check_deserialize_identity() {
let encoded_identity = RistrettoPoint::identity().compress().to_bytes();

let r = <<Ristretto255Sha512 as Ciphersuite>::Group as Group>::deserialize(&encoded_identity);
let r = <Ristretto255Sha512 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::InvalidIdentityElement));
}
1 change: 1 addition & 0 deletions frost-secp256k1/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ bincode = "1"
criterion = "0.4"
ed25519-dalek = "1.0.1"
ed25519-zebra = "3.0.0"
hex = "0.4.3"
lazy_static = "1.4"
proptest = "1.0"
proptest-derive = "0.3"
Expand Down
28 changes: 27 additions & 1 deletion frost-secp256k1/tests/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,32 @@ fn check_deserialize_identity() {
// allow us to change that. Try to send something similar.
let encoded_identity = [0u8; 33];

let r = <<Secp256K1Sha256 as Ciphersuite>::Group as Group>::deserialize(&encoded_identity);
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::MalformedElement));
}

#[test]
fn check_deserialize_non_canonical() {
let mut encoded_generator = <Secp256K1Sha256 as Ciphersuite>::Group::serialize(
&<Secp256K1Sha256 as Ciphersuite>::Group::generator(),
);

let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert!(r.is_ok());

// The first byte should be 0x02 or 0x03. Set other value to
// create a non-canonical encoding.
encoded_generator[0] = 0xFF;
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert_eq!(r, Err(GroupError::MalformedElement));

// Besides the first byte, it is still possible to get non-canonical encodings.
// This is x = p + 2 which is non-canonical and maps to a valid prime-order point.
let encoded_point =
hex::decode("02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc31")
.unwrap()
.try_into()
.unwrap();
let r = <Secp256K1Sha256 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::MalformedElement));
}