From 3617ed4eb7ff44272b0f3f344d2ec0d3dfed041f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 3 Jun 2022 23:22:08 -0400 Subject: [PATCH] Use const values for our traits where we can --- coins/monero/src/frost.rs | 21 ++-------- crypto/frost/src/key_gen.rs | 12 +++--- crypto/frost/src/lib.rs | 55 +++++++++++--------------- crypto/frost/src/schnorr.rs | 8 ++-- crypto/frost/src/sign.rs | 2 +- crypto/frost/src/tests/literal/p256.rs | 21 ++-------- crypto/frost/src/tests/mod.rs | 2 +- crypto/frost/src/tests/schnorr.rs | 10 ++--- crypto/frost/src/tests/vectors.rs | 12 +++--- processor/src/coins/monero.rs | 10 ++--- processor/src/lib.rs | 20 ++++------ processor/src/wallet.rs | 12 +++--- 12 files changed, 72 insertions(+), 113 deletions(-) diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index 6b44a296..4653cc3e 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -57,25 +57,12 @@ impl, const WIDE: bool> Curve for Ed25519Internal u8 { - u8::try_from(Self::id().len()).unwrap() - } + const ID: &'static [u8] = b"edwards25519"; - fn id() -> &'static [u8] { - b"edwards25519" - } + const GENERATOR: Self::G = dfg::ED25519_BASEPOINT_POINT; + const GENERATOR_TABLE: Self::T = &dfg::ED25519_BASEPOINT_TABLE; - fn generator() -> Self::G { - Self::G::generator() - } - - fn generator_table() -> Self::T { - &dfg::ED25519_BASEPOINT_TABLE - } - - fn little_endian() -> bool { - true - } + const LITTLE_ENDIAN: bool = true; fn random_nonce(secret: Self::F, rng: &mut R) -> Self::F { let mut seed = vec![0; 32]; diff --git a/crypto/frost/src/key_gen.rs b/crypto/frost/src/key_gen.rs index 643a2454..c30962de 100644 --- a/crypto/frost/src/key_gen.rs +++ b/crypto/frost/src/key_gen.rs @@ -41,7 +41,7 @@ fn generate_key_r1( // Step 1: Generate t random values to form a polynomial with coefficients.push(C::F::random(&mut *rng)); // Step 3: Generate public commitments - commitments.push(C::generator_table() * coefficients[i]); + commitments.push(C::GENERATOR_TABLE * coefficients[i]); // Serialize them for publication serialized.extend(&C::G_to_bytes(&commitments[i])); } @@ -59,7 +59,7 @@ fn generate_key_r1( challenge::( context, params.i(), - &C::G_to_bytes(&(C::generator_table() * r)), + &C::G_to_bytes(&(C::GENERATOR_TABLE * r)), &serialized ) ).serialize() @@ -224,7 +224,7 @@ fn complete_r2( res }; - let mut batch = BatchVerifier::new(shares.len(), C::little_endian()); + let mut batch = BatchVerifier::new(shares.len(), C::LITTLE_ENDIAN); for (l, share) in &shares { if *l == params.i() { continue; @@ -237,7 +237,7 @@ fn complete_r2( // ensure that malleability isn't present is to use this n * t algorithm, which runs // per sender and not as an aggregate of all senders, which also enables blame let mut values = exponential(params.i, &commitments[l]); - values.push((-*share, C::generator())); + values.push((-*share, C::GENERATOR)); batch.queue(rng, *l, values); } batch.verify_with_vartime_blame().map_err(|l| FrostError::InvalidCommitment(l))?; @@ -254,9 +254,9 @@ fn complete_r2( // Calculate each user's verification share let mut verification_shares = HashMap::new(); for i in 1 ..= params.n() { - verification_shares.insert(i, multiexp_vartime(exponential(i, &stripes), C::little_endian())); + verification_shares.insert(i, multiexp_vartime(exponential(i, &stripes), C::LITTLE_ENDIAN)); } - debug_assert_eq!(C::generator_table() * secret_share, verification_shares[¶ms.i()]); + debug_assert_eq!(C::GENERATOR_TABLE * secret_share, verification_shares[¶ms.i()]); // TODO: Clear serialized and shares diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 54abee1d..3f7c2b4e 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -42,22 +42,19 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug { /// Precomputed table type type T: Mul; - /// Byte length of the curve ID - // While C::id().len() is trivial, this bounds it to u8 for any proper Curve implementation - fn id_len() -> u8; /// ID for this curve - fn id() -> &'static [u8]; + const ID: &'static [u8]; /// Generator for the group - // While group does provide this in its API, Jubjub users will want to use a custom basepoint - fn generator() -> Self::G; + // While group does provide this in its API, privacy coins will want to use a custom basepoint + const GENERATOR: Self::G; /// Table for the generator for the group /// If there isn't a precomputed table available, the generator itself should be used - fn generator_table() -> Self::T; + const GENERATOR_TABLE: Self::T; /// If little endian is used for the scalar field's Repr - fn little_endian() -> bool; + const LITTLE_ENDIAN: bool; /// Securely generate a random nonce. H4 from the IETF draft fn random_nonce(secret: Self::F, rng: &mut R) -> Self::F; @@ -298,12 +295,12 @@ impl MultisigKeys { let offset_share = offset * C::F::from(included.len().try_into().unwrap()).invert().unwrap(); Ok(MultisigView { - group_key: self.group_key + (C::generator_table() * offset), + group_key: self.group_key + (C::GENERATOR_TABLE * offset), secret_share: secret_share + offset_share, verification_shares: self.verification_shares.iter().map( |(l, share)| ( *l, - (*share * lagrange::(*l, &included)) + (C::generator_table() * offset_share) + (*share * lagrange::(*l, &included)) + (C::GENERATOR_TABLE * offset_share) ) ).collect(), included: included.to_vec(), @@ -311,15 +308,13 @@ impl MultisigKeys { } pub fn serialized_len(n: u16) -> usize { - 1 + usize::from(C::id_len()) + (3 * 2) + C::F_len() + C::G_len() + (usize::from(n) * C::G_len()) + 8 + C::ID.len() + (3 * 2) + C::F_len() + C::G_len() + (usize::from(n) * C::G_len()) } pub fn serialize(&self) -> Vec { - let mut serialized = Vec::with_capacity( - 1 + usize::from(C::id_len()) + MultisigKeys::::serialized_len(self.params.n) - ); - serialized.push(C::id_len()); - serialized.extend(C::id()); + let mut serialized = Vec::with_capacity(MultisigKeys::::serialized_len(self.params.n)); + serialized.extend(u64::try_from(C::ID.len()).unwrap().to_be_bytes()); + serialized.extend(C::ID); serialized.extend(&self.params.t.to_be_bytes()); serialized.extend(&self.params.n.to_be_bytes()); serialized.extend(&self.params.i.to_be_bytes()); @@ -328,34 +323,28 @@ impl MultisigKeys { for l in 1 ..= self.params.n.into() { serialized.extend(&C::G_to_bytes(&self.verification_shares[&l])); } - serialized } pub fn deserialize(serialized: &[u8]) -> Result, FrostError> { - if serialized.len() < 1 { - Err(FrostError::InternalError("MultisigKeys serialization is empty".to_string()))?; + let mut start = u64::try_from(C::ID.len()).unwrap().to_be_bytes().to_vec(); + start.extend(C::ID); + let mut cursor = start.len(); + + if serialized.len() < (cursor + 4) { + Err( + FrostError::InternalError( + "MultisigKeys serialization is missing its curve/participant quantities".to_string() + ) + )?; } - - let id_len: usize = serialized[0].into(); - let mut cursor = 1; - - if serialized.len() < (cursor + id_len) { - Err(FrostError::InternalError("ID wasn't included".to_string()))?; - } - - if C::id() != &serialized[cursor .. (cursor + id_len)] { + if &start != &serialized[.. cursor] { Err( FrostError::InternalError( "curve is distinct between serialization and deserialization".to_string() ) )?; } - cursor += id_len; - - if serialized.len() < (cursor + 4) { - Err(FrostError::InternalError("participant quantities weren't included".to_string()))?; - } let t = u16::from_be_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap()); cursor += 2; diff --git a/crypto/frost/src/schnorr.rs b/crypto/frost/src/schnorr.rs index 238d8f4b..c138f05c 100644 --- a/crypto/frost/src/schnorr.rs +++ b/crypto/frost/src/schnorr.rs @@ -28,7 +28,7 @@ pub(crate) fn sign( challenge: C::F ) -> SchnorrSignature { SchnorrSignature { - R: C::generator_table() * nonce, + R: C::GENERATOR_TABLE * nonce, s: nonce + (private_key * challenge) } } @@ -38,15 +38,15 @@ pub(crate) fn verify( challenge: C::F, signature: &SchnorrSignature ) -> bool { - (C::generator_table() * signature.s) == (signature.R + (public_key * challenge)) + (C::GENERATOR_TABLE * signature.s) == (signature.R + (public_key * challenge)) } pub(crate) fn batch_verify( rng: &mut R, triplets: &[(u16, C::G, C::F, SchnorrSignature)] ) -> Result<(), u16> { - let mut values = [(C::F::one(), C::generator()); 3]; - let mut batch = BatchVerifier::new(triplets.len(), C::little_endian()); + let mut values = [(C::F::one(), C::GENERATOR); 3]; + let mut batch = BatchVerifier::new(triplets.len(), C::LITTLE_ENDIAN); for triple in triplets { // s = r + ca // sG == R + cA diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 5ccb139c..11739fc4 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -84,7 +84,7 @@ fn preprocess>( C::random_nonce(params.view().secret_share(), &mut *rng), C::random_nonce(params.view().secret_share(), &mut *rng) ]; - let commitments = [C::generator_table() * nonces[0], C::generator_table() * nonces[1]]; + let commitments = [C::GENERATOR_TABLE * nonces[0], C::GENERATOR_TABLE * nonces[1]]; let mut serialized = C::G_to_bytes(&commitments[0]); serialized.extend(&C::G_to_bytes(&commitments[1])); diff --git a/crypto/frost/src/tests/literal/p256.rs b/crypto/frost/src/tests/literal/p256.rs index d98d4824..1ca4ed39 100644 --- a/crypto/frost/src/tests/literal/p256.rs +++ b/crypto/frost/src/tests/literal/p256.rs @@ -82,25 +82,12 @@ impl Curve for P256 { type G = ProjectivePoint; type T = ProjectivePoint; - fn id_len() -> u8 { - u8::try_from(Self::id().len()).unwrap() - } + const ID: &'static [u8] = b"P-256"; - fn id() -> &'static [u8] { - b"P-256" - } + const GENERATOR: Self::G = Self::G::GENERATOR; + const GENERATOR_TABLE: Self::G = Self::G::GENERATOR; - fn generator() -> Self::G { - Self::G::GENERATOR - } - - fn generator_table() -> Self::T { - Self::G::GENERATOR - } - - fn little_endian() -> bool { - false - } + const LITTLE_ENDIAN: bool = false; fn random_nonce(secret: Self::F, rng: &mut R) -> Self::F { let mut seed = vec![0; 32]; diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index 5a1b58f1..d361d3fd 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -113,7 +113,7 @@ pub fn recover(keys: &HashMap>) -> C::F { C::F::zero(), |accum, (i, keys)| accum + (keys.secret_share() * lagrange::(*i, &included)) ); - assert_eq!(C::generator_table() * group_private, first.group_key(), "failed to recover keys"); + assert_eq!(C::GENERATOR_TABLE * group_private, first.group_key(), "failed to recover keys"); group_private } diff --git a/crypto/frost/src/tests/schnorr.rs b/crypto/frost/src/tests/schnorr.rs index 4b39fcce..2c2ea85e 100644 --- a/crypto/frost/src/tests/schnorr.rs +++ b/crypto/frost/src/tests/schnorr.rs @@ -15,7 +15,7 @@ pub(crate) fn core_sign(rng: &mut R) { let challenge = C::F::random(rng); // Doesn't bother to craft an HRAM assert!( schnorr::verify::( - C::generator_table() * private_key, + C::GENERATOR_TABLE * private_key, challenge, &schnorr::sign(private_key, nonce, challenge) ) @@ -28,9 +28,9 @@ pub(crate) fn core_sign(rng: &mut R) { pub(crate) fn core_verify(rng: &mut R) { assert!( !schnorr::verify::( - C::generator_table() * C::F::random(&mut *rng), + C::GENERATOR_TABLE * C::F::random(&mut *rng), C::F::random(rng), - &SchnorrSignature { R: C::generator_table() * C::F::zero(), s: C::F::zero() } + &SchnorrSignature { R: C::GENERATOR_TABLE * C::F::zero(), s: C::F::zero() } ) ); } @@ -48,7 +48,7 @@ pub(crate) fn core_batch_verify(rng: &mut R) { // Batch verify let triplets = (0 .. 5).map( - |i| (u16::try_from(i + 1).unwrap(), C::generator_table() * keys[i], challenges[i], sigs[i]) + |i| (u16::try_from(i + 1).unwrap(), C::GENERATOR_TABLE * keys[i], challenges[i], sigs[i]) ).collect::>(); schnorr::batch_verify(rng, &triplets).unwrap(); @@ -113,7 +113,7 @@ fn sign_with_offset(rng: &mut R) { for i in 1 ..= u16::try_from(keys.len()).unwrap() { keys.insert(i, Rc::new(keys[&i].offset(offset))); } - let offset_key = group_key + (C::generator_table() * offset); + let offset_key = group_key + (C::GENERATOR_TABLE * offset); sign_core(rng, offset_key, &keys); } diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index 0e9f3396..590d9efa 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -26,14 +26,14 @@ fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap>(); let verification_shares = shares.iter().map( - |secret| C::generator() * secret + |secret| C::GENERATOR * secret ).collect::>(); let mut keys = HashMap::new(); for i in 1 ..= u16::try_from(shares.len()).unwrap() { let mut serialized = vec![]; - serialized.push(C::id_len()); - serialized.extend(C::id()); + serialized.extend(u64::try_from(C::ID.len()).unwrap().to_be_bytes()); + serialized.extend(C::ID); serialized.extend(vectors.threshold.to_be_bytes()); serialized.extend(u16::try_from(shares.len()).unwrap().to_be_bytes()); serialized.extend(i.to_be_bytes()); @@ -59,7 +59,7 @@ pub fn vectors>(vectors: Vectors) { let keys = vectors_to_multisig_keys::(&vectors); let group_key = C::G_from_slice(&hex::decode(vectors.group_key).unwrap()).unwrap(); assert_eq!( - C::generator() * C::F_from_slice(&hex::decode(vectors.group_secret).unwrap()).unwrap(), + C::GENERATOR * C::F_from_slice(&hex::decode(vectors.group_secret).unwrap()).unwrap(), group_key ); assert_eq!( @@ -87,8 +87,8 @@ pub fn vectors>(vectors: Vectors) { C::F_from_slice(&hex::decode(vectors.nonces[c][1]).unwrap()).unwrap() ]; - let mut serialized = C::G_to_bytes(&(C::generator() * nonces[0])); - serialized.extend(&C::G_to_bytes(&(C::generator() * nonces[1]))); + let mut serialized = C::G_to_bytes(&(C::GENERATOR * nonces[0])); + serialized.extend(&C::G_to_bytes(&(C::GENERATOR * nonces[1]))); machine.unsafe_override_preprocess( PreprocessPackage { nonces, serialized: serialized.clone() } diff --git a/processor/src/coins/monero.rs b/processor/src/coins/monero.rs index 834ab63d..c0cb416a 100644 --- a/processor/src/coins/monero.rs +++ b/processor/src/coins/monero.rs @@ -58,7 +58,7 @@ impl Monero { pub fn new(url: String) -> Monero { Monero { rpc: Rpc::new(url), - view: dfg::Scalar::from_hash(view_key::(0)).0 + view: *view_key::(0) } } } @@ -73,16 +73,16 @@ impl Coin for Monero { type Address = Address; - fn id() -> &'static [u8] { b"Monero" } - fn confirmations() -> usize { 10 } + const ID: &'static [u8] = b"Monero"; + const CONFIRMATIONS: usize = 10; // Testnet TX bb4d188a4c571f2f0de70dca9d475abc19078c10ffa8def26dd4f63ce1bcfd79 uses 146 inputs // while using less than 100kb of space, albeit with just 2 outputs (though outputs share a BP) // The TX size limit is half the contextual median block weight, where said weight is >= 300,000 // This means any TX which fits into 150kb will be accepted by Monero // 128, even with 16 outputs, should fit into 100kb. Further efficiency by 192 may be viable // TODO: Get hard numbers and tune - fn max_inputs() -> usize { 128 } - fn max_outputs() -> usize { 16 } + const MAX_INPUTS: usize = 128; + const MAX_OUTPUTS: usize = 16; async fn get_height(&self) -> Result { self.rpc.get_height().await.map_err(|_| CoinError::ConnectionError) diff --git a/processor/src/lib.rs b/processor/src/lib.rs index fae4bfad..3e01dd54 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -4,8 +4,6 @@ use async_trait::async_trait; use thiserror::Error; use rand_core::{RngCore, CryptoRng}; -use blake2::{digest::{Digest, Update}, Blake2b512}; - use frost::{Curve, MultisigKeys}; mod coins; @@ -40,10 +38,10 @@ pub trait Coin { type Address: Send; - fn id() -> &'static [u8]; - fn confirmations() -> usize; - fn max_inputs() -> usize; - fn max_outputs() -> usize; + const ID: &'static [u8]; + const CONFIRMATIONS: usize; + const MAX_INPUTS: usize; + const MAX_OUTPUTS: usize; async fn get_height(&self) -> Result; async fn get_block(&self, height: usize) -> Result; @@ -70,11 +68,9 @@ pub trait Coin { ) -> Result<(Vec, Vec<::Id>), CoinError>; } -// Generate a view key for a given chain in a globally consistent manner regardless of the current -// group key +// Generate a static view key for a given chain in a globally consistent manner +// Doesn't consider the current group key to increase the simplicity of verifying Serai's status // Takes an index, k, for more modern privacy protocols which use multiple view keys -// Doesn't run Curve::hash_to_F, instead returning the hash object, due to hash_to_F being a FROST -// definition instead of a wide reduction from a hash object -pub fn view_key(k: u64) -> Blake2b512 { - Blake2b512::new().chain(b"Serai DEX View Key").chain(C::id()).chain(k.to_le_bytes()) +pub fn view_key(k: u64) -> ::F { + C::Curve::hash_to_F(b"Serai DEX View Key", &[C::ID, &k.to_le_bytes()].concat()) } diff --git a/processor/src/wallet.rs b/processor/src/wallet.rs index d1ab088d..db05a6cd 100644 --- a/processor/src/wallet.rs +++ b/processor/src/wallet.rs @@ -16,7 +16,7 @@ impl WalletKeys { } // Bind this key to a specific network by applying an additive offset - // While it would be fine to just C::id(), including the group key creates distinct + // While it would be fine to just C::ID, including the group key creates distinct // offsets instead of static offsets. Under a statically offset system, a BTC key could // have X subtracted to find the potential group key, and then have Y added to find the // potential ETH group key. While this shouldn't be an issue, as this isn't a private @@ -27,7 +27,7 @@ impl WalletKeys { const DST: &[u8] = b"Serai Processor Wallet Chain Bind"; let mut transcript = DigestTranscript::::new(DST); transcript.append_message(b"chain", chain); - transcript.append_message(b"curve", C::id()); + transcript.append_message(b"curve", C::ID); transcript.append_message(b"group_key", &C::G_to_bytes(&self.keys.group_key())); self.keys.offset(C::hash_to_F(DST, &transcript.challenge(b"offset"))) } @@ -73,11 +73,11 @@ impl Wallet { pub fn add_keys(&mut self, keys: &WalletKeys) { // Doesn't use +1 as this is height, not block index, and poll moves by block index - self.pending.push((self.acknowledged_height(keys.creation_height), keys.bind(C::id()))); + self.pending.push((self.acknowledged_height(keys.creation_height), keys.bind(C::ID))); } pub async fn poll(&mut self) -> Result<(), CoinError> { - let confirmed_height = self.coin.get_height().await? - C::confirmations(); + let confirmed_height = self.coin.get_height().await? - C::CONFIRMATIONS; for height in self.scanned_height() .. confirmed_height { // If any keys activated at this height, shift them over { @@ -114,7 +114,7 @@ impl Wallet { let acknowledged_height = self.acknowledged_height(canonical); - // TODO: Log schedule outputs when max_outputs is low + // TODO: Log schedule outputs when MAX_OUTPUTS is low // Payments is the first set of TXs in the schedule // As each payment re-appears, let mut payments = schedule[payment] where the only input is // the source payment @@ -129,7 +129,7 @@ impl Wallet { while outputs.len() != 0 { // Select the maximum amount of outputs possible - let mut inputs = &outputs[0 .. C::max_inputs().min(outputs.len())]; + let mut inputs = &outputs[0 .. C::MAX_INPUTS.min(outputs.len())]; // Calculate their sum value, minus the fee needed to spend them let mut sum = inputs.iter().map(|input| input.amount()).sum::();