diff --git a/src/key.rs b/src/key.rs index 6ddbf08..4f715d8 100644 --- a/src/key.rs +++ b/src/key.rs @@ -29,6 +29,7 @@ use crate::ffi::types::c_uint; use crate::{Message, ecdsa, SECP256K1}; #[cfg(all(feature = "global-context", feature = "rand-std"))] use crate::schnorr; +use crate::Scalar; /// Secret 256-bit key used as `x` in an ECDSA signature. /// @@ -232,15 +233,11 @@ impl SecretKey { /// /// # Errors /// - /// Returns an error if the resulting key would be invalid or if the tweak was not a 32-byte - /// length slice. + /// Returns an error if the resulting key would be invalid. pub fn add_assign( &mut self, - other: &[u8], + other: &Scalar, ) -> Result<(), Error> { - if other.len() != 32 { - return Err(Error::InvalidTweak); - } unsafe { if ffi::secp256k1_ec_seckey_tweak_add( ffi::secp256k1_context_no_precomp, @@ -257,15 +254,11 @@ impl SecretKey { #[inline] /// Multiplies one secret key by another, modulo the curve order. Will - /// return an error if the resulting key would be invalid or if - /// the tweak was not a 32-byte length slice. + /// return an error if the resulting key would be invalid. pub fn mul_assign( &mut self, - other: &[u8], + other: &Scalar, ) -> Result<(), Error> { - if other.len() != 32 { - return Err(Error::InvalidTweak); - } unsafe { if ffi::secp256k1_ec_seckey_tweak_mul( ffi::secp256k1_context_no_precomp, @@ -498,20 +491,16 @@ impl PublicKey { } #[inline] - /// Adds the `other` public key to `self` in place. + /// Adds `other * G` to `self` in place. /// /// # Errors /// - /// Returns an error if the resulting key would be invalid or if the tweak was not a 32-byte - /// length slice. + /// Returns an error if the resulting key would be invalid. pub fn add_exp_assign( &mut self, secp: &Secp256k1, - other: &[u8] + other: &Scalar ) -> Result<(), Error> { - if other.len() != 32 { - return Err(Error::InvalidTweak); - } unsafe { if ffi::secp256k1_ec_pubkey_tweak_add(secp.ctx, &mut self.0, other.as_c_ptr()) == 1 { Ok(()) @@ -526,16 +515,12 @@ impl PublicKey { /// /// # Errors /// - /// Returns an error if the resulting key would be invalid or if the tweak was not a 32-byte - /// length slice. + /// Returns an error if the resulting key would be invalid. pub fn mul_assign( &mut self, secp: &Secp256k1, - other: &[u8], + other: &Scalar, ) -> Result<(), Error> { - if other.len() != 32 { - return Err(Error::InvalidTweak); - } unsafe { if ffi::secp256k1_ec_pubkey_tweak_mul(secp.ctx, &mut self.0, other.as_c_ptr()) == 1 { Ok(()) @@ -860,8 +845,7 @@ impl KeyPair { /// /// # Errors /// - /// Returns an error if the resulting key would be invalid or if the tweak was not a 32-byte - /// length slice. + /// Returns an error if the resulting key would be invalid. /// /// NB: Will not error if the tweaked public key has an odd value and can't be used for /// BIP 340-342 purposes. @@ -870,12 +854,11 @@ impl KeyPair { /// /// ``` /// # #[cfg(all(feature = "std", feature = "rand-std"))] { - /// use secp256k1::{Secp256k1, KeyPair}; + /// use secp256k1::{Secp256k1, KeyPair, Scalar}; /// use secp256k1::rand::{RngCore, thread_rng}; /// /// let secp = Secp256k1::new(); - /// let mut tweak = [0u8; 32]; - /// thread_rng().fill_bytes(&mut tweak); + /// let tweak = Scalar::random(); /// /// let mut key_pair = KeyPair::new(&secp, &mut thread_rng()); /// key_pair.tweak_add_assign(&secp, &tweak).expect("Improbable to fail with a randomly generated tweak"); @@ -886,12 +869,8 @@ impl KeyPair { pub fn tweak_add_assign( &mut self, secp: &Secp256k1, - tweak: &[u8], + tweak: &Scalar, ) -> Result<(), Error> { - if tweak.len() != 32 { - return Err(Error::InvalidTweak); - } - unsafe { let err = ffi::secp256k1_keypair_xonly_tweak_add( secp.ctx, @@ -1150,12 +1129,11 @@ impl XOnlyPublicKey { /// /// ``` /// # #[cfg(all(feature = "std", feature = "rand-std"))] { - /// use secp256k1::{Secp256k1, KeyPair}; + /// use secp256k1::{Secp256k1, KeyPair, Scalar}; /// use secp256k1::rand::{RngCore, thread_rng}; /// /// let secp = Secp256k1::new(); - /// let mut tweak = [0u8; 32]; - /// thread_rng().fill_bytes(&mut tweak); + /// let tweak = Scalar::random(); /// /// let mut key_pair = KeyPair::new(&secp, &mut thread_rng()); /// let (mut public_key, _parity) = key_pair.x_only_public_key(); @@ -1165,12 +1143,8 @@ impl XOnlyPublicKey { pub fn tweak_add_assign( &mut self, secp: &Secp256k1, - tweak: &[u8], + tweak: &Scalar, ) -> Result { - if tweak.len() != 32 { - return Err(Error::InvalidTweak); - } - let mut pk_parity = 0; unsafe { let mut pubkey = ffi::PublicKey::new(); @@ -1215,12 +1189,11 @@ impl XOnlyPublicKey { /// /// ``` /// # #[cfg(all(feature = "std", feature = "rand-std"))] { - /// use secp256k1::{Secp256k1, KeyPair}; + /// use secp256k1::{Secp256k1, KeyPair, Scalar}; /// use secp256k1::rand::{thread_rng, RngCore}; /// /// let secp = Secp256k1::new(); - /// let mut tweak = [0u8; 32]; - /// thread_rng().fill_bytes(&mut tweak); + /// let tweak = Scalar::random(); /// /// let mut key_pair = KeyPair::new(&secp, &mut thread_rng()); /// let (mut public_key, _) = key_pair.x_only_public_key(); @@ -1234,7 +1207,7 @@ impl XOnlyPublicKey { secp: &Secp256k1, tweaked_key: &Self, tweaked_parity: Parity, - tweak: [u8; 32], + tweak: Scalar, ) -> bool { let tweaked_ser = tweaked_key.serialize(); unsafe { @@ -1512,6 +1485,7 @@ mod test { use super::{XOnlyPublicKey, PublicKey, Secp256k1, SecretKey, KeyPair, Parity}; use crate::{constants, from_hex, to_hex}; use crate::Error::{InvalidPublicKey, InvalidSecretKey}; + use crate::Scalar; macro_rules! hex { ($hex:expr) => ({ @@ -1770,15 +1744,17 @@ mod test { let (mut sk1, mut pk1) = s.generate_keypair(&mut thread_rng()); let (mut sk2, mut pk2) = s.generate_keypair(&mut thread_rng()); + let scalar1 = Scalar::from(sk1); + let scalar2 = Scalar::from(sk1); assert_eq!(PublicKey::from_secret_key(&s, &sk1), pk1); - assert!(sk1.add_assign(&sk2[..]).is_ok()); - assert!(pk1.add_exp_assign(&s, &sk2[..]).is_ok()); + assert!(sk1.add_assign(&scalar2).is_ok()); + assert!(pk1.add_exp_assign(&s, &scalar2).is_ok()); assert_eq!(PublicKey::from_secret_key(&s, &sk1), pk1); assert_eq!(PublicKey::from_secret_key(&s, &sk2), pk2); - assert!(sk2.add_assign(&sk1[..]).is_ok()); - assert!(pk2.add_exp_assign(&s, &sk1[..]).is_ok()); + assert!(sk2.add_assign(&scalar1).is_ok()); + assert!(pk2.add_exp_assign(&s, &scalar1).is_ok()); assert_eq!(PublicKey::from_secret_key(&s, &sk2), pk2); } @@ -1789,15 +1765,17 @@ mod test { let (mut sk1, mut pk1) = s.generate_keypair(&mut thread_rng()); let (mut sk2, mut pk2) = s.generate_keypair(&mut thread_rng()); + let scalar1 = Scalar::from(sk1); + let scalar2 = Scalar::from(sk1); assert_eq!(PublicKey::from_secret_key(&s, &sk1), pk1); - assert!(sk1.mul_assign(&sk2[..]).is_ok()); - assert!(pk1.mul_assign(&s, &sk2[..]).is_ok()); + assert!(sk1.mul_assign(&scalar2).is_ok()); + assert!(pk1.mul_assign(&s, &scalar2).is_ok()); assert_eq!(PublicKey::from_secret_key(&s, &sk1), pk1); assert_eq!(PublicKey::from_secret_key(&s, &sk2), pk2); - assert!(sk2.mul_assign(&sk1[..]).is_ok()); - assert!(pk2.mul_assign(&s, &sk1[..]).is_ok()); + assert!(sk2.mul_assign(&scalar1).is_ok()); + assert!(pk2.mul_assign(&s, &scalar1).is_ok()); assert_eq!(PublicKey::from_secret_key(&s, &sk2), pk2); } @@ -1913,7 +1891,7 @@ mod test { assert!(sum2.is_ok()); assert_eq!(sum1, sum2); - assert!(sk1.add_assign(&sk2.as_ref()[..]).is_ok()); + assert!(sk1.add_assign(&Scalar::from(sk2)).is_ok()); let sksum = PublicKey::from_secret_key(&s, &sk1); assert_eq!(Ok(sksum), sum1); } @@ -1999,8 +1977,7 @@ mod test { let s = Secp256k1::new(); for _ in 0..10 { - let mut tweak = [0u8; 32]; - thread_rng().fill_bytes(&mut tweak); + let tweak = Scalar::random(); let mut kp = KeyPair::new(&s, &mut thread_rng()); let (mut pk, _parity) = kp.x_only_public_key(); diff --git a/src/lib.rs b/src/lib.rs index de3b7a3..f1cc959 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,6 +173,7 @@ mod key; pub mod constants; pub mod ecdh; pub mod ecdsa; +pub mod scalar; pub mod schnorr; #[cfg(feature = "serde")] mod serde_util; @@ -190,6 +191,7 @@ pub use secp256k1_sys as ffi; pub use crate::key::{PublicKey, SecretKey}; pub use crate::context::*; pub use crate::key::*; +pub use crate::scalar::Scalar; #[cfg(feature = "global-context")] #[cfg_attr(docsrs, doc(cfg(feature = "global-context")))] diff --git a/src/scalar.rs b/src/scalar.rs new file mode 100644 index 0000000..b4ef08f --- /dev/null +++ b/src/scalar.rs @@ -0,0 +1,137 @@ +//! Provides [`Scalar`] and related types. +//! +//! In elliptic curve cryptography scalars are non-point values that can be used to multiply +//! points. The most common type of scalars are private keys. However not all scalars are private +//! keys. They can even be public *values*. To make handling them safer and easier this module +//! provides the `Scalar` type and related. + +use core::fmt; + +/// Positive 256-bit integer guaranteed to be less than the secp256k1 curve order. +/// +/// The difference between `PrivateKey` and `Scalar` is that `Scalar` doesn't guarantee being +/// securely usable as a private key. +/// +/// **Warning: the operations on this type are NOT constant time!** +/// Using this with secret values is not advised. +// Internal represenation is big endian to match what `libsecp256k1` uses. +// Also easier to implement comparison. +// Debug impl omitted for now, the bytes may be secret +#[allow(missing_debug_implementations)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct Scalar([u8; 32]); + +const MAX_RAW: [u8; 32] = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x40 +]; + +impl Scalar { + /// Scalar representing `0` + pub const ZERO: Scalar = Scalar([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// Scalar representing `1` + pub const ONE: Scalar = Scalar([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + /// Maximum valid value: `curve_order - 1` + pub const MAX: Scalar = Scalar(MAX_RAW); + + /// Generates a random scalar + #[cfg(any(test, feature = "rand-std"))] + #[cfg_attr(docsrs, doc(cfg(feature = "rand-std")))] + pub fn random() -> Self { + Self::random_custom(rand::thread_rng()) + } + + /// Generates a random scalar using supplied RNG + #[cfg(any(test, feature = "rand"))] + #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] + pub fn random_custom(mut rng: R) -> Self { + let mut bytes = [0u8; 32]; + loop { + rng.fill_bytes(&mut bytes); + // unlikely to go past MAX + if let Ok(scalar) = Scalar::from_be_bytes(bytes) { + break scalar; + } + } + } + + /// Tries to deserialize from big endian bytes + /// + /// **Security warning:** this function is not constant time! + /// Passing secret data is not recommended. + /// + /// # Errors + /// + /// Returns error when the value is above the curve order. + pub fn from_be_bytes(value: [u8; 32]) -> Result { + // Lexicographic ordering of arrays of the same length is same as ordering of BE numbers + if value <= MAX_RAW { + Ok(Scalar(value)) + } else { + Err(OutOfRangeError {}) + } + } + + /// Tries to deserialize from little endian bytes + /// + /// **Security warning:** this function is not constant time! + /// Passing secret data is not recommended. + /// + /// # Errors + /// + /// Returns error when the value is above the curve order. + pub fn from_le_bytes(mut value: [u8; 32]) -> Result { + value.reverse(); + Self::from_be_bytes(value) + } + + /// Serializes to big endian bytes + pub fn to_be_bytes(self) -> [u8; 32] { + self.0 + } + + /// Serializes to little endian bytes + pub fn to_le_bytes(self) -> [u8; 32] { + let mut res = self.0; + res.reverse(); + res + } + + // returns a reference to internal bytes + // non-public to not leak the internal representation + pub(crate) fn as_be_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub(crate) fn as_c_ptr(&self) -> *const u8 { + use secp256k1_sys::CPtr; + + self.as_be_bytes().as_c_ptr() + } +} + +impl From for Scalar { + fn from(value: crate::SecretKey) -> Self { + Scalar(value.secret_bytes()) + } +} + + +/// Error returned when the value of scalar is invalid - larger than the curve order. +// Intentionally doesn't implement `Copy` to improve forward compatibility. +// Same reason for `non_exhaustive`. +#[allow(missing_copy_implementations)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct OutOfRangeError { +} + +impl fmt::Display for OutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt("the value is not a member of secp256k1 field", f) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for OutOfRangeError {}