Skip to content

Commit

Permalink
Change Scalar::from_canonical_bytes to return CtOption
Browse files Browse the repository at this point in the history
This is helpful for implementing `ff::PrimeField::from_repr`.

Also changes `Scalar::is_canonical` to return `Choice`.
  • Loading branch information
tarcieri committed Dec 12, 2022
1 parent 94a99d8 commit e665a4c
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ major series.
* Deprecate `EdwardsPoint::hash_from_bytes` and rename it `EdwardsPoint::nonspec_map_to_curve`
* Require including a new trait, `use curve25519_dalek::traits::BasepointTable`
whenever using `EdwardsBasepointTable` or `RistrettoBasepointTable`
* `Scalar::from_canonical_bytes` now returns `CtOption`
* `Scalar::is_canonical` now returns `Choice`

#### Other changes

Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ required-features = ["rand_core"]
cfg-if = "1"
rand_core = { version = "0.6.4", default-features = false, optional = true }
digest = { version = "0.10", default-features = false, optional = true }
subtle = { version = "^2.2.1", default-features = false }
subtle = { version = "2.3.0", default-features = false }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
zeroize = { version = "1", default-features = false }

Expand All @@ -65,3 +65,6 @@ packed_simd = { version = "0.3.4", package = "packed_simd_2", features = ["into_
[features]
default = ["alloc"]
alloc = ["zeroize/alloc"]

[profile.dev]
opt-level = 2
48 changes: 22 additions & 26 deletions src/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
//! use curve25519_dalek::scalar::Scalar;
//!
//! let one_as_bytes: [u8; 32] = Scalar::ONE.to_bytes();
//! let a: Option<Scalar> = Scalar::from_canonical_bytes(one_as_bytes);
//! let a: Option<Scalar> = Scalar::from_canonical_bytes(one_as_bytes).into();
//!
//! assert!(a.is_some());
//! ```
Expand All @@ -54,7 +54,7 @@
//! 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//! 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
//! ];
//! let a: Option<Scalar> = Scalar::from_canonical_bytes(l_plus_two_bytes);
//! let a: Option<Scalar> = Scalar::from_canonical_bytes(l_plus_two_bytes).into();
//!
//! assert!(a.is_none());
//! ```
Expand Down Expand Up @@ -132,7 +132,7 @@
//! let two: Scalar = Scalar::ONE + Scalar::ONE;
//!
//! assert!(a != two); // the scalar is not reduced (mod l)…
//! assert!(! a.is_canonical()); // …and therefore is not canonical.
//! assert!(! bool::from(a.is_canonical())); // …and therefore is not canonical.
//! assert!(a.reduce() == two); // if we were to reduce it manually, it would be.
//! ```
//!
Expand Down Expand Up @@ -163,6 +163,7 @@ use digest::Digest;
use subtle::Choice;
use subtle::ConditionallySelectable;
use subtle::ConstantTimeEq;
use subtle::CtOption;

use zeroize::Zeroize;

Expand Down Expand Up @@ -256,18 +257,10 @@ impl Scalar {
/// - `Some(s)`, where `s` is the `Scalar` corresponding to `bytes`,
/// if `bytes` is a canonical byte representation;
/// - `None` if `bytes` is not a canonical byte representation.
pub fn from_canonical_bytes(bytes: [u8; 32]) -> Option<Scalar> {
// Check that the high bit is not set
if (bytes[31] >> 7) != 0u8 {
return None;
}
pub fn from_canonical_bytes(bytes: [u8; 32]) -> CtOption<Scalar> {
let high_bit_unset = (bytes[31] >> 7).ct_eq(&0);
let candidate = Scalar::from_bits(bytes);

if candidate.is_canonical() {
Some(candidate)
} else {
None
}
CtOption::new(candidate, high_bit_unset & candidate.is_canonical())
}

/// Construct a `Scalar` from the low 255 bits of a 256-bit integer.
Expand Down Expand Up @@ -457,9 +450,8 @@ impl<'de> Deserialize<'de> for Scalar {
.next_element()?
.ok_or(serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
}
Scalar::from_canonical_bytes(bytes).ok_or(serde::de::Error::custom(
&"scalar was not canonically encoded",
))
Option::from(Scalar::from_canonical_bytes(bytes))
.ok_or_else(|| serde::de::Error::custom(&"scalar was not canonically encoded"))
}
}

Expand Down Expand Up @@ -1133,22 +1125,20 @@ impl Scalar {

/// Check whether this `Scalar` is the canonical representative mod \\(\ell\\).
///
/// This is intended for uses like input validation, where variable-time code is acceptable.
///
/// ```
/// # use curve25519_dalek::scalar::Scalar;
/// # use subtle::ConditionallySelectable;
/// # fn main() {
/// // 2^255 - 1, since `from_bits` clears the high bit
/// let _2_255_minus_1 = Scalar::from_bits([0xff;32]);
/// assert!(!_2_255_minus_1.is_canonical());
/// assert!(! bool::from(_2_255_minus_1.is_canonical()));
///
/// let reduced = _2_255_minus_1.reduce();
/// assert!(reduced.is_canonical());
/// assert!(bool::from(reduced.is_canonical()));
/// # }
/// ```
pub fn is_canonical(&self) -> bool {
*self == self.reduce()
pub fn is_canonical(&self) -> Choice {
self.ct_eq(&self.reduce())
}
}

Expand Down Expand Up @@ -1708,9 +1698,15 @@ mod test {
0, 0, 128,
];

assert!(Scalar::from_canonical_bytes(canonical_bytes).is_some());
assert!(Scalar::from_canonical_bytes(non_canonical_bytes_because_unreduced).is_none());
assert!(Scalar::from_canonical_bytes(non_canonical_bytes_because_highbit).is_none());
assert!(bool::from(
Scalar::from_canonical_bytes(canonical_bytes).is_some()
));
assert!(bool::from(
Scalar::from_canonical_bytes(non_canonical_bytes_because_unreduced).is_none()
));
assert!(bool::from(
Scalar::from_canonical_bytes(non_canonical_bytes_because_highbit).is_none()
));
}

#[test]
Expand Down

0 comments on commit e665a4c

Please sign in to comment.