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

feat!: differentiate scalar parsing from byte arrays #194

Merged
merged 1 commit into from
Sep 28, 2023
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ version = "0.18.0"
edition = "2018"

[dependencies]
tari_utilities = { version = "0.5", default-features = false, features = ["zero"] }
tari_utilities = { version = "0.6", default-features = false, features = ["zero"] }
blake2 = { version = "0.10", default-features = false }
borsh = { version = "0.10" , optional = true , default-features = false}
bulletproofs_plus = { package = "tari_bulletproofs_plus", version = "0.3", optional = true }
Expand All @@ -27,7 +27,7 @@ snafu = { version = "0.7", default-features = false}
zeroize = {version = "1" , default-features = false}

[dev-dependencies]
tari_utilities = { version = "0.5", features = ["std"] }
tari_utilities = { version = "0.6", features = ["std"] }
serde = { version = "1.0"}
bincode = { version = "1.1" }
criterion = { version = "0.5", default-features = false }
Expand Down
6 changes: 3 additions & 3 deletions benches/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn sign_message(c: &mut Criterion) {
b.iter_batched(
gen_keypair,
|d| {
let _sig = RistrettoSchnorr::sign_message(&d.k, d.m, &mut OsRng).unwrap();
let _sig = RistrettoSchnorr::sign(&d.k, d.m, &mut OsRng).unwrap();
},
BatchSize::SmallInput,
);
Expand All @@ -60,10 +60,10 @@ fn verify_message(c: &mut Criterion) {
b.iter_batched(
|| {
let d = gen_keypair();
let s = RistrettoSchnorr::sign_message(&d.k, d.m, &mut OsRng).unwrap();
let s = RistrettoSchnorr::sign(&d.k, d.m, &mut OsRng).unwrap();
(d, s)
},
|(d, s)| assert!(s.verify_message(&d.p, d.m)),
|(d, s)| assert!(s.verify(&d.p, d.m)),
BatchSize::SmallInput,
);
});
Expand Down
4 changes: 2 additions & 2 deletions src/commitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ where P: PublicKey
impl<P> ByteArray for HomomorphicCommitment<P>
where P: PublicKey
{
fn from_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError> {
let p = P::from_bytes(bytes)?;
fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError> {
let p = P::from_canonical_bytes(bytes)?;
Ok(Self(p))
}

Expand Down
2 changes: 1 addition & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub enum HashingError {
reason: String,
},
/// The digest does not produce enough output
#[snafu(display("The digest does produce enough output.`{bytes}' bytes are required."))]
#[snafu(display("The digest does not produce enough output.`{bytes}' bytes are required."))]
DigestTooShort {
/// The number of bytes required
bytes: usize,
Expand Down
46 changes: 31 additions & 15 deletions src/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ use alloc::string::String;
use core::{marker::PhantomData, ops::Deref};

use blake2::{Blake2b, Blake2bVar};
use digest::{consts::U32, Digest, FixedOutput, FixedOutputReset, Output, OutputSizeUser, Update};
use digest::{
consts::{U32, U64},
Digest,
FixedOutput,
FixedOutputReset,
Output,
OutputSizeUser,
Update,
};
use sha3::Sha3_256;
use tari_utilities::ByteArray;

use crate::{
alloc::string::ToString,
Expand Down Expand Up @@ -209,10 +216,10 @@ impl<D: Digest> AsRef<[u8]> for DomainSeparatedHash<D> {
/// Calculating a signature challenge
///
/// ```
/// # use blake2::Blake2b;
/// # use digest::{consts::U32, Digest};
/// # use tari_utilities::hex::{to_hex, Hex};
/// use blake2::{Blake2b, Digest};
/// use digest::consts::U32;
/// use tari_crypto::{
/// # use tari_crypto::{
/// hash_domain,
/// hashing::{DomainSeparatedHash, DomainSeparatedHasher, DomainSeparation},
/// };
Expand Down Expand Up @@ -418,6 +425,8 @@ impl LengthExtensionAttackResistant for Sha3_256 {}

impl LengthExtensionAttackResistant for Blake2b<U32> {}

impl LengthExtensionAttackResistant for Blake2b<U64> {}

//------------------------------------------------ HMAC ------------------------------------------------------------
/// A domain separation tag for use in MAC derivation algorithms.
pub struct MacDomain;
Expand Down Expand Up @@ -517,62 +526,69 @@ impl<D: Digest> Deref for Mac<D> {
/// `RistrettoKdf` is an implementation of [`DerivedKeyDomain`] that generates Ristretto keys.
///
/// ```
/// # use blake2::Blake2b;
/// # use digest::{consts::U64, Digest};
/// # use tari_utilities::ByteArray;
/// # use tari_utilities::hex::Hex;
/// # use tari_crypto::errors::HashingError;
/// # use tari_crypto::hashing::{DerivedKeyDomain, MacDomain};
/// # use tari_crypto::keys::SecretKey;
/// # use tari_crypto::ristretto::ristretto_keys::RistrettoKdf;
/// # use tari_crypto::ristretto::RistrettoSecretKey;
/// # use digest::consts::U32;
/// # use blake2::Blake2b;
///
/// fn wallet_keys(
/// primary_key: &RistrettoSecretKey,
/// index: usize,
/// ) -> Result<RistrettoSecretKey, HashingError> {
/// RistrettoKdf::generate::<Blake2b<U32>>(
/// RistrettoKdf::generate::<Blake2b<U64>>(
/// primary_key.as_bytes(),
/// &index.to_le_bytes(),
/// "wallet",
/// )
/// }
///
/// let key = RistrettoSecretKey::from_hex(
/// "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c",
/// "a8fb609c5ab7cc07548b076b6c25cc3237c4526fb7a6dcb83b26f457b172c20a",
/// )
/// .unwrap();
/// let key_1 = wallet_keys(&key, 1).unwrap();
/// assert_eq!(
/// key_1.to_hex(),
/// "b778b8b5041fbde6c78be5bafd6d62633824bf303c97736d7337b3f6f70c4e0b"
/// "08106b88a2ff4c52d1d8b458cf34802df8655ba989a7d91351e3504e087a2e0c"
/// );
/// let key_64 = wallet_keys(&key, 64).unwrap();
/// assert_eq!(
/// key_64.to_hex(),
/// "09e5204c93406ef3334ff5f7a4d5d84199ceb9119fafcb98928fa95e95f0ae05"
/// "2c2206dadd2a21e71b6c52dd321572cde0f2b00e7116e1123fb580b09ed1b70e"
/// );
/// ```
pub trait DerivedKeyDomain: DomainSeparation {
/// The associated derived secret key type
type DerivedKeyType: SecretKey;

/// Derive a key from the input key using a suitable domain separation tag and the given application label.
/// An error is returned if the supplied primary key isn't at least as long as the digest algorithm's output size.
/// Derive a key from the input key using a suitable domain separation tag and the given application label by wide
/// reduction. An error is returned if the supplied primary key isn't at least as long as the derived key.
/// If the digest's output size is not sufficient to generate the derived key type, then an error will be thrown.
fn generate<D>(primary_key: &[u8], data: &[u8], label: &'static str) -> Result<Self::DerivedKeyType, HashingError>
where
Self: Sized,
D: Digest + Update,
{
if primary_key.as_ref().len() < <D as Digest>::output_size() {
// Ensure the primary key is at least as long as the derived key
if primary_key.len() < <Self::DerivedKeyType as SecretKey>::KEY_LEN {
return Err(HashingError::InputTooShort {});
}

// Ensure the digest length is suitable for wide reduction
if <D as Digest>::output_size() != <Self::DerivedKeyType as SecretKey>::WIDE_REDUCTION_LEN {
return Err(HashingError::InputTooShort {});
}

let hash = DomainSeparatedHasher::<D, Self>::new_with_label(label)
.chain(primary_key)
.chain(data)
.finalize();
let derived_key = Self::DerivedKeyType::from_bytes(hash.as_ref())
let derived_key = Self::DerivedKeyType::from_uniform_bytes(hash.as_ref())
.map_err(|e| HashingError::ConversionFromBytes { reason: e.to_string() })?;
Ok(derived_key)
}
Expand Down
9 changes: 8 additions & 1 deletion src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use core::ops::Add;

use rand_core::{CryptoRng, RngCore};
use tari_utilities::ByteArray;
use tari_utilities::{ByteArray, ByteArrayError};
use zeroize::{Zeroize, ZeroizeOnDrop};

/// A trait specifying common behaviour for representing `SecretKey`s. Specific elliptic curve
Expand All @@ -32,13 +32,20 @@ pub trait SecretKey:
/// The length of the byte encoding of a key, in bytes
const KEY_LEN: usize;

/// The number of bytes used for construction by wide reduction
const WIDE_REDUCTION_LEN: usize;

/// The length of the byte encoding of a key, in bytes
fn key_length() -> usize {
Self::KEY_LEN
}

/// Generates a random secret key
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self;

/// Generates a secret key from a slice of uniformly-distributed bytes using wide reduction
/// If the number of bytes is incorrect, this will fail
fn from_uniform_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError>;
}

//---------------------------------------- Public Keys ----------------------------------------//
Expand Down
1 change: 0 additions & 1 deletion src/ristretto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub mod ristretto_keys;
mod ristretto_sig;
#[cfg(feature = "serde")]
pub mod serialize;
pub mod utils;

pub use self::{
ristretto_com_and_pub_sig::RistrettoComAndPubSig,
Expand Down
2 changes: 1 addition & 1 deletion src/ristretto/pedersen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ mod test {
let (_, p) = RistrettoPublicKey::random_keypair(&mut rng);
let c = PedersenCommitment::from_public_key(&p);
assert_eq!(c.as_public_key(), &p);
let c2 = PedersenCommitment::from_bytes(c.as_bytes()).unwrap();
let c2 = PedersenCommitment::from_canonical_bytes(c.as_bytes()).unwrap();
assert_eq!(c, c2);
}

Expand Down
57 changes: 22 additions & 35 deletions src/ristretto/ristretto_com_and_pub_sig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,18 @@ use crate::{
/// "8063d85e151abee630e643e2b3dc47bfaeb8aa859c9d10d60847985f286aad19",
/// )
/// .unwrap();
/// let u_a = RistrettoSecretKey::from_bytes(b"10000000000000000000000010000000").unwrap();
/// let u_x = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap();
/// let u_y = RistrettoSecretKey::from_bytes(b"a00000000000000000000000a0000000").unwrap();
/// let u_a = RistrettoSecretKey::from_hex(
/// "a8fb609c5ab7cc07548b076b6c25cc3237c4526fb7a6dcb83b26f457b172c20a",
/// )
/// .unwrap();
/// let u_x = RistrettoSecretKey::from_hex(
/// "0e689df8ad4ad9d2fd5aaf8cb0a66d85cb0d4b7a380405514d453625813b0b0f",
/// )
/// .unwrap();
/// let u_y = RistrettoSecretKey::from_hex(
/// "f494050bd0d4ed0ec514cdce9430d0564df6b35d2a12b7daa0e99c7d94a06509",
/// )
/// .unwrap();
/// let sig = RistrettoComAndPubSig::new(ephemeral_commitment, ephemeral_pubkey, u_a, u_x, u_y);
/// ```
///
Expand All @@ -48,7 +57,7 @@ use crate::{
/// # use tari_crypto::ristretto::pedersen::*;
/// use tari_crypto::ristretto::pedersen::commitment_factory::PedersenCommitmentFactory;
/// use tari_utilities::hex::Hex;
/// use digest::consts::U32;
/// use digest::consts::U64;
///
/// let mut rng = rand::thread_rng();
/// let a_val = RistrettoSecretKey::random(&mut rng);
Expand All @@ -57,7 +66,7 @@ use crate::{
/// let a_nonce = RistrettoSecretKey::random(&mut rng);
/// let x_nonce = RistrettoSecretKey::random(&mut rng);
/// let y_nonce = RistrettoSecretKey::random(&mut rng);
/// let e = Blake2b::<U32>::digest(b"Maskerade"); // In real life, this should be strong Fiat-Shamir!
/// let e = Blake2b::<U64>::digest(b"Maskerade"); // In real life, this should be strong Fiat-Shamir!
/// let factory = PedersenCommitmentFactory::default();
/// let commitment = factory.commit(&x_val, &a_val);
/// let pubkey = RistrettoPublicKey::from_secret_key(&y_val);
Expand All @@ -72,8 +81,8 @@ pub type RistrettoComAndPubSig = CommitmentAndPublicKeySignature<RistrettoPublic
#[cfg(test)]
mod test {
use blake2::Blake2b;
use digest::{consts::U32, Digest};
use tari_utilities::{hex::from_hex, ByteArray};
use digest::{consts::U64, Digest};
use tari_utilities::ByteArray;

use crate::{
commitment::HomomorphicCommitmentFactory,
Expand Down Expand Up @@ -132,15 +141,15 @@ mod test {
let ephemeral_commitment = factory.commit(&r_x, &r_a);
let ephemeral_pubkey = RistrettoPublicKey::from_secret_key(&r_y);

// Challenge; doesn't use proper Fiat-Shamir, so it's for testing only!
let challenge = Blake2b::<U32>::new()
// Challenge; doesn't use domain-separated Fiat-Shamir, so it's for testing only!
let challenge = Blake2b::<U64>::new()
.chain_update(commitment.as_bytes())
.chain_update(pubkey.as_bytes())
.chain_update(ephemeral_commitment.as_bytes())
.chain_update(ephemeral_pubkey.as_bytes())
.chain_update(b"Small Gods")
.finalize();
let e_key = RistrettoSecretKey::from_bytes(&challenge).unwrap();
let e_key = RistrettoSecretKey::from_uniform_bytes(&challenge).unwrap();

// Responses
let u_a = &r_a + e_key.clone() * &a_value;
Expand Down Expand Up @@ -178,7 +187,7 @@ mod test {
assert!(!sig.verify_challenge(&commitment, &evil_pubkey, &challenge, &factory, &mut rng));

// A different challenge should fail
let evil_challenge = Blake2b::<U32>::digest(b"Guards! Guards!");
let evil_challenge = Blake2b::<U64>::digest(b"Guards! Guards!");
assert!(!sig.verify_challenge(&commitment, &pubkey, &evil_challenge, &factory, &mut rng));
}

Expand All @@ -205,7 +214,7 @@ mod test {
let ephemeral_pubkey = RistrettoPublicKey::from_secret_key(&r_y);

// Challenge; doesn't use proper Fiat-Shamir, so it's for testing only!
let challenge = Blake2b::<U32>::new()
let challenge = Blake2b::<U64>::new()
.chain_update(commitment.as_bytes())
.chain_update(pubkey.as_bytes())
.chain_update(ephemeral_commitment.as_bytes())
Expand Down Expand Up @@ -292,7 +301,7 @@ mod test {
let ephemeral_pubkey_bob = RistrettoPublicKey::from_secret_key(&r_y_bob);

// The challenge is common to Alice and Bob; here we use an arbitrary hash
let challenge = Blake2b::<U32>::digest(b"Test challenge");
let challenge = Blake2b::<U64>::digest(b"Test challenge");

// Alice's signature
let sig_alice = RistrettoComAndPubSig::sign(
Expand Down Expand Up @@ -338,28 +347,6 @@ mod test {
assert!(sig_sum.verify_challenge(&commitment_sum, &pubkey_sum, &challenge, &factory, &mut rng))
}

/// Ristretto scalars have a max value 2^255. This test checks that hashed messages above this value can still be
/// signed as a result of applying modulo arithmetic on the challenge value
#[test]
fn challenge_from_invalid_scalar() {
let mut rng = rand::thread_rng();
let factory = PedersenCommitmentFactory::default();

let a_value = RistrettoSecretKey::random(&mut rng);
let x_value = RistrettoSecretKey::random(&mut rng);
let y_value = RistrettoSecretKey::random(&mut rng);

let message = from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();

let r_a = RistrettoSecretKey::random(&mut rng);
let r_x = RistrettoSecretKey::random(&mut rng);
let r_y = RistrettoSecretKey::random(&mut rng);

assert!(
RistrettoComAndPubSig::sign(&a_value, &x_value, &y_value, &r_a, &r_x, &r_y, &message, &factory).is_ok()
);
}

#[test]
fn to_vec() {
let sig = RistrettoComAndPubSig::default();
Expand Down
Loading