From 94f380f8575d4d3bdaa60483f384e8cd4a6fbbc9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 12 Jul 2022 02:45:18 -0400 Subject: [PATCH 1/8] Update to FROST v7 --- crypto/frost/src/algorithm.rs | 2 +- crypto/frost/src/sign.rs | 55 ++++++++++++++++--------- crypto/frost/src/tests/literal/dalek.rs | 32 +++++++------- crypto/frost/src/tests/literal/kp256.rs | 16 +++---- 4 files changed, 60 insertions(+), 45 deletions(-) diff --git a/crypto/frost/src/algorithm.rs b/crypto/frost/src/algorithm.rs index 12f48e52..ee8021f2 100644 --- a/crypto/frost/src/algorithm.rs +++ b/crypto/frost/src/algorithm.rs @@ -69,7 +69,7 @@ impl Transcript for IetfTranscript { type Challenge = Vec; fn new(_: &'static [u8]) -> IetfTranscript { - unimplemented!("IetfTranscript should not be used with multiple nonce protocols"); + IetfTranscript(vec![]) } fn domain_separate(&mut self, _: &[u8]) {} diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index eab8a035..4176819b 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -130,8 +130,7 @@ fn preprocess>( #[allow(non_snake_case)] struct Package { - B: HashMap>>, - binding: C::F, + B: HashMap>, C::F)>, Rs: Vec>, share: Vec } @@ -156,19 +155,16 @@ fn sign_with_share>( let transcript = params.algorithm.transcript(); // Domain separate FROST transcript.domain_separate(b"FROST"); - // Include the offset, if one exists - if let Some(offset) = params.keys.offset { - transcript.append_message(b"offset", offset.to_repr().as_ref()); - } } #[allow(non_snake_case)] let mut B = HashMap::::with_capacity(params.view.included.len()); - // Get the binding factor + // Get the binding factors let nonces = params.algorithm.nonces(); let mut addendums = HashMap::new(); - let binding = { + + { let transcript = params.algorithm.transcript(); // Parse the commitments for l in ¶ms.view.included { @@ -218,15 +214,34 @@ fn sign_with_share>( addendums.insert(*l, serialized[c ..].to_vec()); } - B.insert(*l, commitments); + B.insert(*l, (commitments, C::F::zero())); } - // Append the message to the transcript - transcript.append_message(b"message", &C::hash_msg(&msg)); + // Re-format into the FROST-expected rho transcript + let mut rho_transcript = A::Transcript::new(b"FROST_rho"); + rho_transcript.append_message(b"message", &C::hash_msg(&msg)); + rho_transcript.append_message( + b"commitments", + &C::hash_msg(transcript.challenge(b"commitments").as_ref()) + ); + // Include the offset, if one exists + // While this isn't part of the FROST-expected rho transcript, the offset being here coincides + // with another specification + if let Some(offset) = params.keys.offset { + rho_transcript.append_message(b"offset", offset.to_repr().as_ref()); + } - // Calculate the binding factor - C::hash_binding_factor(transcript.challenge(b"binding").as_ref()) - }; + // Generate the per-signer binding factors + for (l, commitments) in B.iter_mut() { + let mut rho_transcript = rho_transcript.clone(); + rho_transcript.append_message(b"participant", &l.to_be_bytes()); + commitments.1 = C::hash_binding_factor(rho_transcript.challenge(b"rho").as_ref()); + } + + // Merge the rho transcript back into the global one to ensure its advanced while committing to + // everything + transcript.append_message(b"rho_transcript", rho_transcript.challenge(b"merge").as_ref()); + } // Process the addendums for l in ¶ms.view.included { @@ -240,8 +255,8 @@ fn sign_with_share>( #[allow(non_snake_case)] for g in 0 .. nonces[n].len() { Rs[n][g] = { - B.values().map(|B| B[n][g][0]).sum::() + - (B.values().map(|B| B[n][g][1]).sum::() * binding) + B.values().map(|(B, _)| B[n][g][0]).sum::() + + B.values().map(|(B, binding)| B[n][g][1] * binding).sum::() }; } } @@ -250,12 +265,12 @@ fn sign_with_share>( ¶ms.view, &Rs, &our_preprocess.nonces.iter().map( - |nonces| nonces[0] + (nonces[1] * binding) + |nonces| nonces[0] + (nonces[1] * B[¶ms.keys.params.i()].1) ).collect::>(), msg ).to_repr().as_ref().to_vec(); - Ok((Package { B, binding, Rs, share: share.clone() }, share)) + Ok((Package { B, Rs, share: share.clone() }, share)) } fn complete>( @@ -287,9 +302,9 @@ fn complete>( for l in &sign_params.view.included { if !sign_params.algorithm.verify_share( sign_params.view.verification_share(*l), - &sign.B[l].iter().map( + &sign.B[l].0.iter().map( |nonces| nonces.iter().map( - |commitments| commitments[0] + (commitments[1] * sign.binding) + |commitments| commitments[0] + (commitments[1] * sign.B[l].1) ).collect() ).collect::>(), responses[l] diff --git a/crypto/frost/src/tests/literal/dalek.rs b/crypto/frost/src/tests/literal/dalek.rs index fdcc0c0f..3342efc4 100644 --- a/crypto/frost/src/tests/literal/dalek.rs +++ b/crypto/frost/src/tests/literal/dalek.rs @@ -21,20 +21,20 @@ fn ristretto_vectors() { included: &[1, 3], nonces: &[ [ - "b358743151e33d84bf00c12f71808f4103957c3e2cabab7b895c436b5e70f90c", - "7bd112153b9ae1ab9b31f5e78f61f5c4ca9ee67b7ea6d1181799c409d14c350c" + "eb0dc12ae7b746d36e3f2de46ce3833a05b9d4af5434eeb8cafaefda76906d00", + "491e91aa9df514ef598d5e0c7c5cdd088fbde4965b96069d546c0f04f1822b03" ], [ - "22acad88478e0d0373a991092a322ebd1b9a2dad90451a976d0db3215426af0e", - "9155e3d7bcf7cd468b980c7e20b2c77cbdfbe33a1dcae031fd8bc6b1403f4b04" + "abd12b8e6f255ee1e540eab029003a6e956567617720f61115f0941615892209", + "218e22625f93f262f025bd2d13c46ba722aa29fe585ceed66ff442d98fe4e509" ] ], sig_shares: &[ - "ff801b4e0839faa67f16dee4127b9f7fbcf5fd007900257b0e2bbc02cbe5e709", - "afdf5481023c855bf3411a5c8a5fafa92357296a078c3b80dc168f294cb4f504" + "efae3a83437fa8cd96194aacc56a7eb841630c280da99e7764a81d1340323306", + "96ddc4582e45eabce46f07b9e9375f8b49d35d1510fd34ac02b1e79d6100a602" ], - sig: "deae61af10e8ee48ba492573592fba547f5debeff6bd6e2024e8673584746f5e".to_owned() + - "ae6070cf0a757f027358f8409dda4e29e04c276b808c60fbea414b2c179add0e" + sig: "7ec584cef9a383afb43883b73bcaa6313afe878bd5fe75a608311b866a76ec67".to_owned() + + "858cffdb71c4928a7b895165afa2dd438b366a3d1da6d323675905b1a132d908" } ); } @@ -58,20 +58,20 @@ fn ed25519_vectors() { included: &[1, 3], nonces: &[ [ - "8c76af04340e83bb5fc427c117d38347fc8ef86d5397feea9aa6412d96c05b0a", - "14a37ddbeae8d9e9687369e5eb3c6d54f03dc19d76bb54fb5425131bc37a600b" + "d9aad97e1a1127bb87702ce8d81d8c07c7cbca89e784868d8e3876ff6b459700", + "5063be2774520d08a5ccd7f1213fb1179a5fa292bf13bc91cb28e7bd4d4a690c" ], [ - "5ca39ebab6874f5e7b5089f3521819a2aa1e2cf738bae6974ee80555de2ef70e", - "0afe3650c4815ff37becd3c6948066e906e929ea9b8f546c74e10002dbcc150c" + "86961f3a429ac0c5696f49e6d796817ff653f83c07f34e9e1f4d4c8c515b7900", + "72225ec11c1315d9f1ea0e78b1160ed95800fadd0191d23fd2f2c90ac96cb307" ] ], sig_shares: &[ - "4369474a398aa10357b60d683da91ea6a767dcf53fd541a8ed6b4d780827ea0a", - "32fcc690d926075e45d2dfb746bab71447943cddbefe80d122c39174aa2e1004" + "caae171b83bff0c2c6f56a1276892918ba228146f6344b85d2ec6efeb6f16d0d", + "ea6fdbf61683cf5f1f742e1b91583f0f667f0369efd2e33399b96d5a3ff0300d" ], - sig: "2b8d9c6995333c5990e3a3dd6568785539d3322f7f0376452487ea35cfda587b".to_owned() + - "75650edb12b1a8619c88ed1f8463d6baeefb18d3fed3c279102fdfecb255fa0e" + sig: "5da10008c13c04dd72328ba8e0f72b63cad43c3bf4b7eaada1c78225afbd977e".to_owned() + + "c74afdb47fdfadca0fcda18a28e8891220a284afe5072fb96ba6dc58f6e19e0a" } ); } diff --git a/crypto/frost/src/tests/literal/kp256.rs b/crypto/frost/src/tests/literal/kp256.rs index dee20157..f9640ce4 100644 --- a/crypto/frost/src/tests/literal/kp256.rs +++ b/crypto/frost/src/tests/literal/kp256.rs @@ -36,20 +36,20 @@ fn p256_vectors() { included: &[1, 3], nonces: &[ [ - "081617b24375e069b39f649d4c4ce2fba6e38b73e7c16759de0b6079a22c4c7e", - "4de5fb77d99f03a2491a83a6a4cb91ca3c82a3f34ce94cec939174f47c9f95dd" + "33a519cf070a166f9ef41a798d03423743f3e7d0b0efd5d0d963773c4c53205e", + "307d208d0c5728f323ae374f1ebd7f14a1a49b77d9d4bc1eab222218a17765ff" ], [ - "d186ea92593f83ea83181b184d41aa93493301ac2bc5b4b1767e94d2db943e38", - "486e2ee25a3fbc8e6399d748b077a2755fde99fa85cc24fa647ea4ebf5811a15" + "a614eadb972dc37b88aeceb6e899903f3104742d13f379a0e014541decbea4a4", + "e509791018504c5bb87edaf0f44761cc840888507c4cd80237971d78e65f70f2" ] ], sig_shares: &[ - "9e4d8865faf8c7b3193a3b35eda3d9e12118447114b1e7d5b4809ea28067f8a9", - "b7d094eab6305ae74daeed1acd31abba9ab81f638d38b72c132cb25a5dfae1fc" + "61e8b9c474df2e66ad19fd80a6e6cec1c6fe43c0a1cffd2d1c28299e93e1bbdb", + "9651d355ca1dea2557ba1f73e38a9f4ff1f1afc565323ef27f88a9d14df8370e" ], - sig: "0342c14c77f9d4ef9b8bd64fb0d7bbfdb9f8216a44e5f7bbe6ac0f3ed5e1a57367".to_owned() + - "561e1d51b129229966e92850bad5859bfee96926fad3007cd3f38639e1ffb554" + sig: "02dfba781e17b830229ae4ed22ebe402873683d9dfd945d01762217fb3172c2a7".to_owned() + + "1f83a8d1a3efd188c04d41cf48a716e11b8eff38607023c1f9bb0d36fe1d9f2e9" } ); } From 3e5cb5ea1f19641c20cb256999cace16f85eb996 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 12 Jul 2022 03:20:50 -0400 Subject: [PATCH 2/8] Add the proposed IETF secp256k1 FROST vectors as NonIetf --- crypto/frost/src/curve/kp256.rs | 2 +- crypto/frost/src/tests/literal/kp256.rs | 45 ++++++++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/crypto/frost/src/curve/kp256.rs b/crypto/frost/src/curve/kp256.rs index bb3e02ed..1afc39e8 100644 --- a/crypto/frost/src/curve/kp256.rs +++ b/crypto/frost/src/curve/kp256.rs @@ -101,5 +101,5 @@ kp_curve!( Secp256k1, NonIetfSecp256k1Hram, b"secp256k1", - b"FROST-secp256k1-SHA256-v5" + b"FROST-secp256k1-SHA256-v7" ); diff --git a/crypto/frost/src/tests/literal/kp256.rs b/crypto/frost/src/tests/literal/kp256.rs index f9640ce4..7866070d 100644 --- a/crypto/frost/src/tests/literal/kp256.rs +++ b/crypto/frost/src/tests/literal/kp256.rs @@ -1,20 +1,49 @@ use rand::rngs::OsRng; -#[cfg(feature = "secp256k1")] -use crate::tests::{curve::test_curve, schnorr::test_schnorr}; -#[cfg(feature = "secp256k1")] -use crate::curve::Secp256k1; - -#[cfg(feature = "p256")] +#[cfg(any(feature = "secp256k1", feature = "p256"))] use crate::tests::vectors::{Vectors, test_with_vectors}; + +#[cfg(feature = "secp256k1")] +use crate::curve::{Secp256k1, NonIetfSecp256k1Hram}; + #[cfg(feature = "p256")] use crate::curve::{P256, IetfP256Hram}; #[cfg(feature = "secp256k1")] #[test] fn secp256k1_non_ietf() { - test_curve::<_, Secp256k1>(&mut OsRng); - test_schnorr::<_, Secp256k1>(&mut OsRng); + test_with_vectors::<_, Secp256k1, NonIetfSecp256k1Hram>( + &mut OsRng, + Vectors { + threshold: 2, + shares: &[ + "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c", + "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984", + "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" + ], + group_secret: "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", + group_key: "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", + + msg: "74657374", + included: &[1, 3], + nonces: &[ + [ + "31c3c1b76b76664569859b9251fbabed9d4d432c6f5aaa03ed41f9c231935798", + "206f4ffaeb602ccb57cbe50e146ac690e6d7317d4b93377061d9d1b4caf78a26" + ], + [ + "0d3945bc1553676a5dd910cb4f14437d99ed421516b2617357b984820fdca520", + "635e0fd90caaf40b5e986d0ee0f58778e4d88731bc6ac70350ef702ffe20a21b" + ] + ], + sig_shares: &[ + "18b71e284c5d008896ed8847b234ec829eda376d6208838ee7faf2ce21b154c1", + "a452a49c8116124d0a283f3589a96b704894b43246e47e59d376353bcc638311" + ], + sig: "03dafb28ee7ad033fd15ed470d07156617260d74a9d76a15d371d7b613d2b111e".to_owned() + + "7bd09c2c4cd7312d5a115c77d3bde57f2e76eeb9fa8ed01e8bb712809ee14d7d2" + } + ); } #[cfg(feature = "p256")] From 0b55fb6e06d099ae2b5ebc800edcf2943148075e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 12 Jul 2022 03:21:22 -0400 Subject: [PATCH 3/8] Use a multiexp to calculate the FROST group nonce --- crypto/frost/src/sign.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 4176819b..7a00de7e 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -3,10 +3,11 @@ use std::{sync::Arc, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; -use group::{ff::{Field, PrimeField}, Group, GroupEncoding}; - use transcript::Transcript; +use group::{ff::{Field, PrimeField}, Group, GroupEncoding}; +use multiexp::multiexp_vartime; + use dleq::{Generators, DLEqProof}; use crate::{ @@ -252,12 +253,16 @@ fn sign_with_share>( let mut Rs = Vec::with_capacity(nonces.len()); for n in 0 .. nonces.len() { Rs.push(vec![C::G::identity(); nonces[n].len()]); - #[allow(non_snake_case)] for g in 0 .. nonces[n].len() { - Rs[n][g] = { - B.values().map(|(B, _)| B[n][g][0]).sum::() + - B.values().map(|(B, binding)| B[n][g][1] * binding).sum::() - }; + #[allow(non_snake_case)] + let mut D = C::G::identity(); + let mut statements = Vec::with_capacity(B.len()); + #[allow(non_snake_case)] + for (B, binding) in B.values() { + D += B[n][g][0]; + statements.push((*binding, B[n][g][1])); + } + Rs[n][g] = D + multiexp_vartime(&statements); } } From d81f6270c7f7d17451f2f5706d8e14e9a5a88b46 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 12 Jul 2022 03:38:59 -0400 Subject: [PATCH 4/8] Version bump and synchronize packages Uses "dleq-serai", instead of "dleq", as the dleq crate name hasn't been transferred yet :( --- coins/monero/Cargo.toml | 2 +- crypto/dalek-ff-group/Cargo.toml | 2 +- crypto/dleq/Cargo.toml | 2 +- crypto/dleq/src/tests/cross_group/mod.rs | 10 +++++----- crypto/dleq/src/tests/cross_group/schnorr.rs | 2 +- crypto/dleq/src/tests/mod.rs | 2 +- crypto/frost/Cargo.toml | 6 +++--- crypto/frost/src/sign.rs | 2 ++ crypto/multiexp/Cargo.toml | 2 +- crypto/transcript/Cargo.toml | 2 +- processor/Cargo.toml | 2 ++ 11 files changed, 19 insertions(+), 15 deletions(-) diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index 10001ea5..ba0be578 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -31,7 +31,7 @@ dalek-ff-group = { path = "../../crypto/dalek-ff-group" } transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"], optional = true } frost = { package = "modular-frost", path = "../../crypto/frost", features = ["ed25519"], optional = true } -dleq = { path = "../../crypto/dleq", features = ["serialize"], optional = true } +dleq = { package = "dleq-serai", path = "../../crypto/dleq", features = ["serialize"], optional = true } hex = "0.4" serde = { version = "1.0", features = ["derive"] } diff --git a/crypto/dalek-ff-group/Cargo.toml b/crypto/dalek-ff-group/Cargo.toml index a0625ca1..1e88927a 100644 --- a/crypto/dalek-ff-group/Cargo.toml +++ b/crypto/dalek-ff-group/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dalek-ff-group" -version = "0.1.1" +version = "0.1.2" description = "ff/group bindings around curve25519-dalek" license = "MIT" repository = "https://github.com/serai-dex/serai" diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index e1a018d1..131f6189 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dleq" +name = "dleq-serai" version = "0.1.0" description = "Implementation of single and cross-curve Discrete Log Equality proofs" license = "MIT" diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index 9e7043f6..bf9b8548 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -7,9 +7,9 @@ use group::{Group, GroupEncoding}; use blake2::{Digest, Blake2b512}; use k256::{Scalar, ProjectivePoint}; -use dalek_ff_group::{self as dfg, EdwardsPoint, CompressedEdwardsY}; +use dalek_ff_group::{self as dfg, EdwardsPoint}; -use transcript::RecommendedTranscript; +use transcript::{Transcript, RecommendedTranscript}; use crate::{ Generators, @@ -41,9 +41,9 @@ pub(crate) fn generators() -> (Generators, Generators) { Generators::new( EdwardsPoint::generator(), - CompressedEdwardsY::new( - hex!("8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94") - ).decompress().unwrap() + EdwardsPoint::from_bytes( + &hex!("8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94") + ).unwrap() ) ) } diff --git a/crypto/dleq/src/tests/cross_group/schnorr.rs b/crypto/dleq/src/tests/cross_group/schnorr.rs index 857044db..f45d85b4 100644 --- a/crypto/dleq/src/tests/cross_group/schnorr.rs +++ b/crypto/dleq/src/tests/cross_group/schnorr.rs @@ -3,7 +3,7 @@ use rand_core::OsRng; use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup}; use multiexp::BatchVerifier; -use transcript::RecommendedTranscript; +use transcript::{Transcript, RecommendedTranscript}; use crate::cross_group::schnorr::SchnorrPoK; diff --git a/crypto/dleq/src/tests/mod.rs b/crypto/dleq/src/tests/mod.rs index 781c4840..27e53b4b 100644 --- a/crypto/dleq/src/tests/mod.rs +++ b/crypto/dleq/src/tests/mod.rs @@ -9,7 +9,7 @@ use group::GroupEncoding; use k256::{Scalar, ProjectivePoint}; -use transcript::RecommendedTranscript; +use transcript::{Transcript, RecommendedTranscript}; use crate::{Generators, DLEqProof}; diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index e68c166b..95304001 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "modular-frost" -version = "0.1.0" +version = "0.2.0" description = "Modular implementation of FROST over ff/group" license = "MIT" repository = "https://github.com/serai-dex/serai" @@ -26,9 +26,9 @@ dalek-ff-group = { path = "../dalek-ff-group", version = "0.1", optional = true transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" } -multiexp = { path = "../multiexp", version = "0.1", features = ["batch"] } +multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] } -dleq = { package = "dleq", path = "../dleq", version = "0.1", features = ["serialize"] } +dleq = { package = "dleq-serai", path = "../dleq", version = "0.1", features = ["serialize"] } [dev-dependencies] rand = "0.8" diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 7a00de7e..05be2d30 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -221,6 +221,8 @@ fn sign_with_share>( // Re-format into the FROST-expected rho transcript let mut rho_transcript = A::Transcript::new(b"FROST_rho"); rho_transcript.append_message(b"message", &C::hash_msg(&msg)); + // This won't just be the commitments, yet the full existing transcript if used in an extended + // protocol rho_transcript.append_message( b"commitments", &C::hash_msg(transcript.challenge(b"commitments").as_ref()) diff --git a/crypto/multiexp/Cargo.toml b/crypto/multiexp/Cargo.toml index 0342f0ee..67d89b92 100644 --- a/crypto/multiexp/Cargo.toml +++ b/crypto/multiexp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multiexp" -version = "0.1.0" +version = "0.2.0" description = "Multiexponentation algorithms for ff/group" license = "MIT" repository = "https://github.com/serai-dex/serai" diff --git a/crypto/transcript/Cargo.toml b/crypto/transcript/Cargo.toml index 06790af4..777a0c5f 100644 --- a/crypto/transcript/Cargo.toml +++ b/crypto/transcript/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flexible-transcript" -version = "0.1.2" +version = "0.1.3" description = "A simple transcript trait definition, along with viable options" license = "MIT" repository = "https://github.com/serai-dex/serai" diff --git a/processor/Cargo.toml b/processor/Cargo.toml index c0a88e4f..df4dbcb7 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -3,7 +3,9 @@ name = "serai-processor" version = "0.1.0" description = "Multichain processor premised on canonicity to reach distributed consensus automatically" license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/processor" authors = ["Luke Parker "] +keywords = [] edition = "2021" publish = false From c0c891569839525fd4b67d6aecb285c1dc2b683c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 12 Jul 2022 03:42:45 -0400 Subject: [PATCH 5/8] Add missing Cargo.toml flags --- crypto/dleq/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index 131f6189..340130d0 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -17,7 +17,7 @@ transcript = { package = "flexible-transcript", path = "../transcript", version ff = "0.12" group = "0.12" -multiexp = { path = "../multiexp", features = ["batch"], optional = true } +multiexp = { path = "../multiexp", version = "0.2", features = ["batch"], optional = true } [dev-dependencies] hex-literal = "0.3" From 6cc8ce840eb68f65ffbc2cf89fafee7dcb6f75dd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 13 Jul 2022 02:38:29 -0400 Subject: [PATCH 6/8] Move FROST to Read Fixes https://github.com/serai-dex/serai/issues/33 and https://github.com/serai-dex/serai/issues/35. Also fixes a few potential panics/DoS AFAICT. --- coins/monero/src/frost.rs | 15 +- coins/monero/src/ringct/clsag/multisig.rs | 18 +- coins/monero/src/wallet/send/multisig.rs | 82 ++++----- crypto/frost/src/algorithm.rs | 9 +- crypto/frost/src/curve/kp256.rs | 26 +-- crypto/frost/src/curve/mod.rs | 63 ++++--- crypto/frost/src/key_gen.rs | 112 ++++++------ crypto/frost/src/lib.rs | 101 +++++------ crypto/frost/src/schnorr.rs | 4 +- crypto/frost/src/sign.rs | 207 +++++++++++----------- crypto/frost/src/tests/curve.rs | 4 +- crypto/frost/src/tests/mod.rs | 12 +- crypto/frost/src/tests/vectors.rs | 53 +++--- 13 files changed, 357 insertions(+), 349 deletions(-) diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index 82b61a65..262a782a 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, io::Cursor}; +use std::io::Read; use thiserror::Error; use rand_core::{RngCore, CryptoRng}; @@ -47,17 +47,14 @@ pub(crate) fn write_dleq( } #[allow(non_snake_case)] -pub(crate) fn read_dleq( - serialized: &[u8], +pub(crate) fn read_dleq( + serialized: &mut Re, H: EdwardsPoint, l: u16, xG: dfg::EdwardsPoint ) -> Result { - if serialized.len() != 96 { - Err(MultisigError::InvalidDLEqProof(l))?; - } - - let bytes = (&serialized[.. 32]).try_into().unwrap(); + let mut bytes = [0; 32]; + serialized.read_exact(&mut bytes).map_err(|_| MultisigError::InvalidDLEqProof(l))?; // dfg ensures the point is torsion free let xH = Option::::from( dfg::EdwardsPoint::from_bytes(&bytes)).ok_or(MultisigError::InvalidDLEqProof(l) @@ -68,7 +65,7 @@ pub(crate) fn read_dleq( } DLEqProof::::deserialize( - &mut Cursor::new(&serialized[32 ..]) + serialized ).map_err(|_| MultisigError::InvalidDLEqProof(l))?.verify( &mut transcript(), Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index 5d5748ea..f6e29756 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -1,5 +1,5 @@ use core::fmt::Debug; -use std::sync::{Arc, RwLock}; +use std::{io::Read, sync::{Arc, RwLock}}; use rand_core::{RngCore, CryptoRng, SeedableRng}; use rand_chacha::ChaCha12Rng; @@ -104,7 +104,7 @@ impl ClsagMultisig { ) } - pub fn serialized_len() -> usize { + pub const fn serialized_len() -> usize { 32 + (2 * 32) } @@ -136,17 +136,12 @@ impl Algorithm for ClsagMultisig { serialized } - fn process_addendum( + fn process_addendum( &mut self, view: &FrostView, l: u16, - serialized: &[u8] + serialized: &mut Re ) -> Result<(), FrostError> { - if serialized.len() != Self::serialized_len() { - // Not an optimal error but... - Err(FrostError::InvalidCommitment(l))?; - } - if self.image.is_identity().into() { self.transcript.domain_separate(b"CLSAG"); self.input().transcript(&mut self.transcript); @@ -154,13 +149,14 @@ impl Algorithm for ClsagMultisig { } self.transcript.append_message(b"participant", &l.to_be_bytes()); - self.transcript.append_message(b"key_image_share", &serialized[.. 32]); - self.image += read_dleq( + let image = read_dleq( serialized, self.H, l, view.verification_share(l) ).map_err(|_| FrostError::InvalidCommitment(l))?.0; + self.transcript.append_message(b"key_image_share", image.compress().to_bytes().as_ref()); + self.image += image; Ok(()) } diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index 33dee744..9db8e074 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -1,4 +1,4 @@ -use std::{sync::{Arc, RwLock}, collections::HashMap}; +use std::{io::{Read, Cursor}, sync::{Arc, RwLock}, collections::HashMap}; use rand_core::{RngCore, CryptoRng, SeedableRng}; use rand_chacha::ChaCha12Rng; @@ -202,57 +202,57 @@ impl PreprocessMachine for TransactionMachine { impl SignMachine for TransactionSignMachine { type SignatureMachine = TransactionSignatureMachine; - fn sign( + fn sign( mut self, - mut commitments: HashMap>, + mut commitments: HashMap, msg: &[u8] ) -> Result<(TransactionSignatureMachine, Vec), FrostError> { if msg.len() != 0 { Err( FrostError::InternalError( - "message was passed to the TransactionMachine when it generates its own".to_string() + "message was passed to the TransactionMachine when it generates its own" ) )?; } - // Add all commitments to the transcript for their entropy - // While each CLSAG will do this as they need to for security, they have their own transcripts - // cloned from this TX's initial premise's transcript. For our TX transcript to have the CLSAG - // data for entropy, it'll have to be added ourselves - commitments.insert(self.i, self.our_preprocess); - for l in &self.included { - self.transcript.append_message(b"participant", &(*l).to_be_bytes()); - // FROST itself will error if this is None, so let it - if let Some(preprocess) = commitments.get(l) { - self.transcript.append_message(b"preprocess", preprocess); - } - } - // FROST commitments and their DLEqs, and the image and its DLEq - let clsag_len = (2 * (32 + 32)) + (2 * (32 + 32)) + ClsagMultisig::serialized_len(); - for (l, commitments) in &commitments { - if commitments.len() != (self.clsags.len() * clsag_len) { - Err(FrostError::InvalidCommitment(*l))?; - } - } + const CLSAG_LEN: usize = (2 * (32 + 32)) + (2 * (32 + 32)) + ClsagMultisig::serialized_len(); // Convert the unified commitments to a Vec of the individual commitments - let mut commitments = (0 .. self.clsags.len()).map(|_| commitments.iter_mut().map( - |(l, commitments)| (*l, commitments.drain(.. clsag_len).collect::>()) - ).collect::>()).collect::>(); - - // Calculate the key images - // Clsag will parse/calculate/validate this as needed, yet doing so here as well provides - // the easiest API overall, as this is where the TX is (which needs the key images in its - // message), along with where the outputs are determined (where our change output needs these - // to be unique) let mut images = vec![EdwardsPoint::identity(); self.clsags.len()]; - for c in 0 .. self.clsags.len() { - for (l, preprocess) in &commitments[c] { + let mut commitments = (0 .. self.clsags.len()).map(|c| { + let mut buf = [0; CLSAG_LEN]; + (&self.included).iter().map(|l| { + // Add all commitments to the transcript for their entropy + // While each CLSAG will do this as they need to for security, they have their own transcripts + // cloned from this TX's initial premise's transcript. For our TX transcript to have the CLSAG + // data for entropy, it'll have to be added ourselves here + self.transcript.append_message(b"participant", &(*l).to_be_bytes()); + if *l == self.i { + buf.copy_from_slice(self.our_preprocess.drain(.. CLSAG_LEN).as_slice()); + } else { + commitments.get_mut(l).ok_or(FrostError::MissingParticipant(*l))? + .read_exact(&mut buf).map_err(|_| FrostError::InvalidCommitment(*l))?; + } + self.transcript.append_message(b"preprocess", &buf); + + // While here, calculate the key image + // Clsag will parse/calculate/validate this as needed, yet doing so here as well provides + // the easiest API overall, as this is where the TX is (which needs the key images in its + // message), along with where the outputs are determined (where our outputs may need + // these in order to guarantee uniqueness) images[c] += CompressedEdwardsY( - preprocess[(clsag_len - 96) .. (clsag_len - 64)].try_into().map_err(|_| FrostError::InvalidCommitment(*l))? + buf[(CLSAG_LEN - 96) .. (CLSAG_LEN - 64)].try_into().map_err(|_| FrostError::InvalidCommitment(*l))? ).decompress().ok_or(FrostError::InvalidCommitment(*l))?; - } + + Ok((*l, Cursor::new(buf))) + }).collect::, _>>() + }).collect::, _>>()?; + + // Remove our preprocess which shouldn't be here. It was just the easiest way to implement the + // above + for map in commitments.iter_mut() { + map.remove(&self.i); } // Create the actual transaction @@ -345,16 +345,18 @@ impl SignMachine for TransactionSignMachine { } impl SignatureMachine for TransactionSignatureMachine { - fn complete(self, mut shares: HashMap>) -> Result { + fn complete(self, mut shares: HashMap) -> Result { let mut tx = self.tx; match tx.rct_signatures.prunable { RctPrunable::Null => panic!("Signing for RctPrunable::Null"), RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => { for clsag in self.clsags { let (clsag, pseudo_out) = clsag.complete( - shares.iter_mut().map( - |(l, shares)| (*l, shares.drain(.. 32).collect()) - ).collect::>() + shares.iter_mut().map(|(l, shares)| { + let mut buf = [0; 32]; + shares.read_exact(&mut buf).map_err(|_| FrostError::InvalidShare(*l))?; + Ok((*l, Cursor::new(buf))) + }).collect::, _>>()? )?; clsags.push(clsag); pseudo_outs.push(pseudo_out); diff --git a/crypto/frost/src/algorithm.rs b/crypto/frost/src/algorithm.rs index ee8021f2..fb09dd0f 100644 --- a/crypto/frost/src/algorithm.rs +++ b/crypto/frost/src/algorithm.rs @@ -1,4 +1,5 @@ use core::{marker::PhantomData, fmt::Debug}; +use std::io::Read; use rand_core::{RngCore, CryptoRng}; @@ -28,11 +29,11 @@ pub trait Algorithm: Clone { ) -> Vec; /// Proccess the addendum for the specified participant. Guaranteed to be ordered - fn process_addendum( + fn process_addendum( &mut self, params: &FrostView, l: u16, - serialized: &[u8], + reader: &mut Re, ) -> Result<(), FrostError>; /// Sign a share with the given secret/nonce @@ -133,11 +134,11 @@ impl> Algorithm for Schnorr { vec![] } - fn process_addendum( + fn process_addendum( &mut self, _: &FrostView, _: u16, - _: &[u8], + _: &mut Re, ) -> Result<(), FrostError> { Ok(()) } diff --git a/crypto/frost/src/curve/kp256.rs b/crypto/frost/src/curve/kp256.rs index 1afc39e8..c5fce6ec 100644 --- a/crypto/frost/src/curve/kp256.rs +++ b/crypto/frost/src/curve/kp256.rs @@ -1,3 +1,5 @@ +use std::io::Cursor; + use rand_core::{RngCore, CryptoRng}; use sha2::{digest::Update, Digest, Sha256}; @@ -6,7 +8,7 @@ use group::{ff::Field, GroupEncoding}; use elliptic_curve::{bigint::{Encoding, U384}, hash2curve::{Expander, ExpandMsg, ExpandMsgXmd}}; -use crate::{curve::{Curve, F_from_slice}, algorithm::Hram}; +use crate::{curve::Curve, algorithm::Hram}; macro_rules! kp_curve { ( @@ -58,16 +60,18 @@ macro_rules! kp_curve { let mut modulus = vec![0; 16]; modulus.extend((Self::F::zero() - Self::F::one()).to_bytes()); let modulus = U384::from_be_slice(&modulus).wrapping_add(&U384::ONE); - F_from_slice::( - &U384::from_be_slice(&{ - let mut bytes = [0; 48]; - ExpandMsgXmd::::expand_message( - &[msg], - dst, - 48 - ).unwrap().fill_bytes(&mut bytes); - bytes - }).reduce(&modulus).unwrap().to_be_bytes()[16 ..] + Self::read_F( + &mut Cursor::new( + &U384::from_be_slice(&{ + let mut bytes = [0; 48]; + ExpandMsgXmd::::expand_message( + &[msg], + dst, + 48 + ).unwrap().fill_bytes(&mut bytes); + bytes + }).reduce(&modulus).unwrap().to_be_bytes()[16 ..] + ) ).unwrap() } } diff --git a/crypto/frost/src/curve/mod.rs b/crypto/frost/src/curve/mod.rs index 32b8fef0..224e81b0 100644 --- a/crypto/frost/src/curve/mod.rs +++ b/crypto/frost/src/curve/mod.rs @@ -1,4 +1,5 @@ use core::fmt::Debug; +use std::io::Read; use thiserror::Error; @@ -77,41 +78,37 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug { // hash_msg and hash_binding_factor #[allow(non_snake_case)] fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; -} -#[allow(non_snake_case)] -pub(crate) fn F_len() -> usize { - ::Repr::default().as_ref().len() -} - -#[allow(non_snake_case)] -pub(crate) fn G_len() -> usize { - ::Repr::default().as_ref().len() -} - -/// Field element from slice -#[allow(non_snake_case)] -pub(crate) fn F_from_slice(slice: &[u8]) -> Result { - let mut encoding = F::Repr::default(); - encoding.as_mut().copy_from_slice(slice); - - let point = Option::::from(F::from_repr(encoding)).ok_or(CurveError::InvalidScalar)?; - if point.to_repr().as_ref() != slice { - Err(CurveError::InvalidScalar)?; + #[allow(non_snake_case)] + fn F_len() -> usize { + ::Repr::default().as_ref().len() } - Ok(point) -} -/// Group element from slice -#[allow(non_snake_case)] -pub(crate) fn G_from_slice(slice: &[u8]) -> Result { - let mut encoding = G::Repr::default(); - encoding.as_mut().copy_from_slice(slice); - - let point = Option::::from(G::from_bytes(&encoding)).ok_or(CurveError::InvalidPoint)?; - // Ban the identity, per the FROST spec, and non-canonical points - if (point.is_identity().into()) || (point.to_bytes().as_ref() != slice) { - Err(CurveError::InvalidPoint)?; + #[allow(non_snake_case)] + fn G_len() -> usize { + ::Repr::default().as_ref().len() + } + + #[allow(non_snake_case)] + fn read_F(r: &mut R) -> Result { + let mut encoding = ::Repr::default(); + r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidScalar)?; + // ff mandates this is canonical + Option::::from(Self::F::from_repr(encoding)).ok_or(CurveError::InvalidScalar) + } + + #[allow(non_snake_case)] + fn read_G(r: &mut R) -> Result { + let mut encoding = ::Repr::default(); + r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidPoint)?; + + let point = Option::::from( + Self::G::from_bytes(&encoding) + ).ok_or(CurveError::InvalidPoint)?; + // Ban the identity, per the FROST spec, and non-canonical points + if (point.is_identity().into()) || (point.to_bytes().as_ref() != encoding.as_ref()) { + Err(CurveError::InvalidPoint)?; + } + Ok(point) } - Ok(point) } diff --git a/crypto/frost/src/key_gen.rs b/crypto/frost/src/key_gen.rs index 84f4a590..7fb00f18 100644 --- a/crypto/frost/src/key_gen.rs +++ b/crypto/frost/src/key_gen.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, collections::HashMap}; +use std::{marker::PhantomData, io::{Read, Cursor}, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; @@ -7,7 +7,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding}; use multiexp::{multiexp_vartime, BatchVerifier}; use crate::{ - curve::{Curve, F_len, G_len, F_from_slice, G_from_slice}, + curve::Curve, FrostError, FrostParams, FrostKeys, schnorr::{self, SchnorrSignature}, validate_map @@ -31,11 +31,11 @@ fn generate_key_r1( rng: &mut R, params: &FrostParams, context: &str, -) -> (Vec, Vec) { +) -> (Vec, Vec, Vec) { let t = usize::from(params.t); let mut coefficients = Vec::with_capacity(t); let mut commitments = Vec::with_capacity(t); - let mut serialized = Vec::with_capacity((G_len::() * t) + G_len::() + F_len::()); + let mut serialized = Vec::with_capacity((C::G_len() * t) + C::G_len() + C::F_len()); for i in 0 .. t { // Step 1: Generate t random values to form a polynomial with @@ -66,58 +66,55 @@ fn generate_key_r1( ); // Step 4: Broadcast - (coefficients, serialized) + (coefficients, commitments, serialized) } // Verify the received data from the first round of key generation -fn verify_r1( +fn verify_r1( rng: &mut R, params: &FrostParams, context: &str, - our_commitments: Vec, - mut serialized: HashMap>, + our_commitments: Vec, + mut serialized: HashMap, ) -> Result>, FrostError> { - validate_map( - &mut serialized, - &(1 ..= params.n()).into_iter().collect::>(), - (params.i(), our_commitments) - )?; - - let commitments_len = usize::from(params.t()) * G_len::(); + validate_map(&mut serialized, &(1 ..= params.n()).collect::>(), params.i())?; let mut commitments = HashMap::new(); - - #[allow(non_snake_case)] - let R_bytes = |l| &serialized[&l][commitments_len .. commitments_len + G_len::()]; - #[allow(non_snake_case)] - let R = |l| G_from_slice::(R_bytes(l)).map_err(|_| FrostError::InvalidProofOfKnowledge(l)); - #[allow(non_snake_case)] - let Am = |l| &serialized[&l][0 .. commitments_len]; - - let s = |l| F_from_slice::( - &serialized[&l][commitments_len + G_len::() ..] - ).map_err(|_| FrostError::InvalidProofOfKnowledge(l)); + commitments.insert(params.i, our_commitments); let mut signatures = Vec::with_capacity(usize::from(params.n() - 1)); for l in 1 ..= params.n() { + if l == params.i { + continue; + } + + let invalid = FrostError::InvalidCommitment(l.try_into().unwrap()); + + // Read the entire list of commitments as the key we're providing a PoK for (A) and the message + #[allow(non_snake_case)] + let mut Am = vec![0; usize::from(params.t()) * C::G_len()]; + serialized.get_mut(&l).unwrap().read_exact(&mut Am).map_err(|_| invalid)?; + let mut these_commitments = vec![]; - for c in 0 .. usize::from(params.t()) { - these_commitments.push( - G_from_slice::( - &serialized[&l][(c * G_len::()) .. ((c + 1) * G_len::())] - ).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))? - ); + let mut cursor = Cursor::new(&Am); + for _ in 0 .. usize::from(params.t()) { + these_commitments.push(C::read_G(&mut cursor).map_err(|_| invalid)?); } // Don't bother validating our own proof of knowledge if l != params.i() { + let cursor = serialized.get_mut(&l).unwrap(); + #[allow(non_snake_case)] + let R = C::read_G(cursor).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?; + let s = C::read_F(cursor).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?; + // Step 5: Validate each proof of knowledge // This is solely the prep step for the latter batch verification signatures.push(( l, these_commitments[0], - challenge::(context, l, R_bytes(l), Am(l)), - SchnorrSignature:: { R: R(l)?, s: s(l)? } + challenge::(context, l, R.to_bytes().as_ref(), &Am), + SchnorrSignature:: { R, s } )); } @@ -147,15 +144,15 @@ fn polynomial( // Implements round 1, step 5 and round 2, step 1 of FROST key generation // Returns our secret share part, commitments for the next step, and a vector for each // counterparty to receive -fn generate_key_r2( +fn generate_key_r2( rng: &mut R, params: &FrostParams, context: &str, coefficients: Vec, - our_commitments: Vec, - commitments: HashMap>, + our_commitments: Vec, + commitments: HashMap, ) -> Result<(C::F, HashMap>, HashMap>), FrostError> { - let commitments = verify_r1::(rng, params, context, our_commitments, commitments)?; + let commitments = verify_r1::<_, _, C>(rng, params, context, our_commitments, commitments)?; // Step 1: Generate secret shares for all other parties let mut res = HashMap::new(); @@ -188,25 +185,21 @@ fn generate_key_r2( /// issue, yet simply confirming protocol completion without issue is enough to confirm the same /// key was generated as long as a lack of duplicated commitments was also confirmed when they were /// broadcasted initially -fn complete_r2( +fn complete_r2( rng: &mut R, params: FrostParams, mut secret_share: C::F, commitments: HashMap>, - // Vec to preserve ownership - mut serialized: HashMap>, + mut serialized: HashMap, ) -> Result, FrostError> { - validate_map( - &mut serialized, - &(1 ..= params.n()).into_iter().collect::>(), - (params.i(), secret_share.to_repr().as_ref().to_vec()) - )?; + validate_map(&mut serialized, &(1 ..= params.n()).collect::>(), params.i())?; // Step 2. Verify each share let mut shares = HashMap::new(); - for (l, share) in serialized { - shares.insert(l, F_from_slice::(&share).map_err(|_| FrostError::InvalidShare(l))?); + for (l, share) in serialized.iter_mut() { + shares.insert(*l, C::read_F(share).map_err(|_| FrostError::InvalidShare(*l))?); } + shares.insert(params.i(), secret_share); // Calculate the exponent for a given participant and apply it to a series of commitments // Initially used with the actual commitments to verify the secret share, later used with stripes @@ -282,7 +275,7 @@ pub struct SecretShareMachine { params: FrostParams, context: String, coefficients: Vec, - our_commitments: Vec, + our_commitments: Vec, } pub struct KeyMachine { @@ -303,15 +296,20 @@ impl KeyGenMachine { /// channel. If any party submits multiple sets of commitments, they MUST be treated as malicious pub fn generate_coefficients( self, - rng: &mut R + rng: &mut R, ) -> (SecretShareMachine, Vec) { - let (coefficients, serialized) = generate_key_r1::(rng, &self.params, &self.context); + let ( + coefficients, + our_commitments, + serialized + ) = generate_key_r1::<_, C>(rng, &self.params, &self.context); + ( SecretShareMachine { params: self.params, context: self.context, coefficients, - our_commitments: serialized.clone() + our_commitments, }, serialized, ) @@ -324,12 +322,12 @@ impl SecretShareMachine { /// index = Vec index. An empty vector is expected at index 0 to allow for this. An empty vector /// is also expected at index i which is locally handled. Returns a byte vector representing a /// secret share for each other participant which should be encrypted before sending - pub fn generate_secret_shares( + pub fn generate_secret_shares( self, rng: &mut R, - commitments: HashMap>, + commitments: HashMap, ) -> Result<(KeyMachine, HashMap>), FrostError> { - let (secret, commitments, shares) = generate_key_r2::( + let (secret, commitments, shares) = generate_key_r2::<_, _, C>( rng, &self.params, &self.context, @@ -348,10 +346,10 @@ impl KeyMachine { /// group's public key, while setting a valid secret share inside the machine. > t participants /// must report completion without issue before this key can be considered usable, yet you should /// wait for all participants to report as such - pub fn complete( + pub fn complete( self, rng: &mut R, - shares: HashMap>, + shares: HashMap, ) -> Result, FrostError> { complete_r2(rng, self.params, self.secret, self.commitments, shares) } diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 40363153..53ce7075 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -1,5 +1,5 @@ use core::fmt::Debug; -use std::collections::HashMap; +use std::{io::Read, collections::HashMap}; use thiserror::Error; @@ -8,7 +8,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding}; mod schnorr; pub mod curve; -use curve::{Curve, F_len, G_len, F_from_slice, G_from_slice}; +use curve::Curve; pub mod key_gen; pub mod algorithm; pub mod sign; @@ -54,7 +54,7 @@ impl FrostParams { pub fn i(&self) -> u16 { self.i } } -#[derive(Clone, Error, Debug)] +#[derive(Copy, Clone, Error, Debug)] pub enum FrostError { #[error("a parameter was 0 (required {0}, participants {1})")] ZeroParameter(u16, u16), @@ -66,11 +66,11 @@ pub enum FrostError { InvalidParticipantIndex(u16, u16), #[error("invalid signing set ({0})")] - InvalidSigningSet(String), + InvalidSigningSet(&'static str), #[error("invalid participant quantity (expected {0}, got {1})")] InvalidParticipantQuantity(usize, usize), #[error("duplicated participant index ({0})")] - DuplicatedIndex(usize), + DuplicatedIndex(u16), #[error("missing participant {0}")] MissingParticipant(u16), #[error("invalid commitment (participant {0})")] @@ -81,7 +81,7 @@ pub enum FrostError { InvalidShare(u16), #[error("internal error ({0})")] - InternalError(String), + InternalError(&'static str), } // View of keys passable to algorithm implementations @@ -182,7 +182,7 @@ impl FrostKeys { pub fn view(&self, included: &[u16]) -> Result, FrostError> { if (included.len() < self.params.t.into()) || (usize::from(self.params.n) < included.len()) { - Err(FrostError::InvalidSigningSet("invalid amount of participants included".to_string()))?; + Err(FrostError::InvalidSigningSet("invalid amount of participants included"))?; } let secret_share = self.secret_share * lagrange::(self.params.i, &included); @@ -203,12 +203,12 @@ impl FrostKeys { } pub fn serialized_len(n: u16) -> usize { - 8 + C::ID.len() + (3 * 2) + F_len::() + G_len::() + (usize::from(n) * 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(FrostKeys::::serialized_len(self.params.n)); - serialized.extend(u64::try_from(C::ID.len()).unwrap().to_be_bytes()); + serialized.extend(u32::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()); @@ -221,59 +221,51 @@ impl FrostKeys { serialized } - pub fn deserialize(serialized: &[u8]) -> Result, FrostError> { - let mut start = u64::try_from(C::ID.len()).unwrap().to_be_bytes().to_vec(); - start.extend(C::ID); - let mut cursor = start.len(); + pub fn deserialize(cursor: &mut R) -> Result, FrostError> { + { + let missing = FrostError::InternalError("FrostKeys serialization is missing its curve"); + let different = FrostError::InternalError("deserializing FrostKeys for another curve"); - if serialized.len() < (cursor + 4) { - Err( - FrostError::InternalError( - "FrostKeys serialization is missing its curve/participant quantities".to_string() - ) - )?; - } - if &start != &serialized[.. cursor] { - Err( - FrostError::InternalError( - "curve is distinct between serialization and deserialization".to_string() - ) - )?; + let mut id_len = [0; 4]; + cursor.read_exact(&mut id_len).map_err(|_| missing)?; + if u32::try_from(C::ID.len()).unwrap().to_be_bytes() != id_len { + Err(different)?; + } + + let mut id = vec![0; C::ID.len()]; + cursor.read_exact(&mut id).map_err(|_| missing)?; + if &id != &C::ID { + Err(different)?; + } } - let t = u16::from_be_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap()); - cursor += 2; + let (t, n, i) = { + let mut read_u16 = || { + let mut value = [0; 2]; + cursor.read_exact(&mut value).map_err( + |_| FrostError::InternalError("missing participant quantities") + )?; + Ok(u16::from_be_bytes(value)) + }; + (read_u16()?, read_u16()?, read_u16()?) + }; - let n = u16::from_be_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap()); - cursor += 2; - if serialized.len() != FrostKeys::::serialized_len(n) { - Err(FrostError::InternalError("incorrect serialization length".to_string()))?; - } - - let i = u16::from_be_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap()); - cursor += 2; - - let secret_share = F_from_slice::(&serialized[cursor .. (cursor + F_len::())]) - .map_err(|_| FrostError::InternalError("invalid secret share".to_string()))?; - cursor += F_len::(); - let group_key = G_from_slice::(&serialized[cursor .. (cursor + G_len::())]) - .map_err(|_| FrostError::InternalError("invalid group key".to_string()))?; - cursor += G_len::(); + let secret_share = C::read_F(cursor) + .map_err(|_| FrostError::InternalError("invalid secret share"))?; + let group_key = C::read_G(cursor).map_err(|_| FrostError::InternalError("invalid group key"))?; let mut verification_shares = HashMap::new(); for l in 1 ..= n { verification_shares.insert( l, - G_from_slice::(&serialized[cursor .. (cursor + G_len::())]) - .map_err(|_| FrostError::InternalError("invalid verification share".to_string()))? + C::read_G(cursor).map_err(|_| FrostError::InternalError("invalid verification share"))? ); - cursor += G_len::(); } Ok( FrostKeys { params: FrostParams::new(t, n, i) - .map_err(|_| FrostError::InternalError("invalid parameters".to_string()))?, + .map_err(|_| FrostError::InternalError("invalid parameters"))?, secret_share, group_key, verification_shares, @@ -287,15 +279,20 @@ impl FrostKeys { pub(crate) fn validate_map( map: &mut HashMap, included: &[u16], - ours: (u16, T) + ours: u16 ) -> Result<(), FrostError> { - map.insert(ours.0, ours.1); - - if map.len() != included.len() { - Err(FrostError::InvalidParticipantQuantity(included.len(), map.len()))?; + if (map.len() + 1) != included.len() { + Err(FrostError::InvalidParticipantQuantity(included.len(), map.len() + 1))?; } for included in included { + if *included == ours { + if map.contains_key(included) { + Err(FrostError::DuplicatedIndex(*included))?; + } + continue; + } + if !map.contains_key(included) { Err(FrostError::MissingParticipant(*included))?; } diff --git a/crypto/frost/src/schnorr.rs b/crypto/frost/src/schnorr.rs index 6bc63768..bf1af240 100644 --- a/crypto/frost/src/schnorr.rs +++ b/crypto/frost/src/schnorr.rs @@ -4,7 +4,7 @@ use group::{ff::{Field, PrimeField}, GroupEncoding}; use multiexp::BatchVerifier; -use crate::{Curve, F_len, G_len}; +use crate::Curve; #[allow(non_snake_case)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -15,7 +15,7 @@ pub struct SchnorrSignature { impl SchnorrSignature { pub fn serialize(&self) -> Vec { - let mut res = Vec::with_capacity(G_len::() + F_len::()); + let mut res = Vec::with_capacity(C::G_len() + C::F_len()); res.extend(self.R.to_bytes().as_ref()); res.extend(self.s.to_repr().as_ref()); res diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 05be2d30..9aacf55f 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -1,5 +1,5 @@ use core::fmt; -use std::{sync::Arc, collections::HashMap}; +use std::{io::{Read, Cursor}, sync::Arc, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; @@ -11,9 +11,8 @@ use multiexp::multiexp_vartime; use dleq::{Generators, DLEqProof}; use crate::{ - curve::{Curve, F_len, G_len, F_from_slice, G_from_slice}, - FrostError, - FrostParams, FrostKeys, FrostView, + curve::Curve, + FrostError, FrostParams, FrostKeys, FrostView, algorithm::Algorithm, validate_map }; @@ -38,7 +37,7 @@ impl> Params { // Included < threshold if included.len() < usize::from(keys.params.t) { - Err(FrostError::InvalidSigningSet("not enough signers".to_string()))?; + Err(FrostError::InvalidSigningSet("not enough signers"))?; } // Invalid index if included[0] == 0 { @@ -51,12 +50,12 @@ impl> Params { // Same signer included multiple times for i in 0 .. included.len() - 1 { if included[i] == included[i + 1] { - Err(FrostError::DuplicatedIndex(included[i].into()))?; + Err(FrostError::DuplicatedIndex(included[i]))?; } } // Not included if !included.contains(&keys.params.i) { - Err(FrostError::InvalidSigningSet("signing despite not being included".to_string()))?; + Err(FrostError::InvalidSigningSet("signing despite not being included"))?; } // Out of order arguments to prevent additional cloning @@ -78,7 +77,8 @@ fn nonce_transcript() -> T { pub(crate) struct PreprocessPackage { pub(crate) nonces: Vec<[C::F; 2]>, - pub(crate) serialized: Vec, + pub(crate) commitments: Vec>, + pub(crate) addendum: Vec, } // This library unifies the preprocessing step with signing due to security concerns and to provide @@ -86,26 +86,29 @@ pub(crate) struct PreprocessPackage { fn preprocess>( rng: &mut R, params: &mut Params, -) -> PreprocessPackage { - let mut serialized = Vec::with_capacity(2 * G_len::()); - let nonces = params.algorithm.nonces().iter().cloned().map( +) -> (PreprocessPackage, Vec) { + let mut serialized = Vec::with_capacity(2 * C::G_len()); + let (nonces, commitments) = params.algorithm.nonces().iter().cloned().map( |mut generators| { let nonces = [ C::random_nonce(params.view().secret_share(), &mut *rng), C::random_nonce(params.view().secret_share(), &mut *rng) ]; - let commit = |generator: C::G| { + let commit = |generator: C::G, buf: &mut Vec| { let commitments = [generator * nonces[0], generator * nonces[1]]; - [commitments[0].to_bytes().as_ref(), commitments[1].to_bytes().as_ref()].concat().to_vec() + buf.extend(commitments[0].to_bytes().as_ref()); + buf.extend(commitments[1].to_bytes().as_ref()); + commitments }; + let mut commitments = Vec::with_capacity(generators.len()); let first = generators.remove(0); - serialized.extend(commit(first)); + commitments.push(commit(first, &mut serialized)); // Iterate over the rest for generator in generators.iter() { - serialized.extend(commit(*generator)); + commitments.push(commit(*generator, &mut serialized)); // Provide a DLEq to verify these commitments are for the same nonce // TODO: Provide a single DLEq. See https://github.com/serai-dex/serai/issues/34 for nonce in nonces { @@ -120,102 +123,109 @@ fn preprocess>( } } - nonces + (nonces, commitments) } - ).collect::>(); + ).unzip(); - serialized.extend(¶ms.algorithm.preprocess_addendum(rng, ¶ms.view)); + let addendum = params.algorithm.preprocess_addendum(rng, ¶ms.view); + serialized.extend(&addendum); - PreprocessPackage { nonces, serialized } + (PreprocessPackage { nonces, commitments, addendum }, serialized) +} + +#[allow(non_snake_case)] +fn read_D_E(cursor: &mut Re, l: u16) -> Result<[C::G; 2], FrostError> { + Ok([ + C::read_G(cursor).map_err(|_| FrostError::InvalidCommitment(l))?, + C::read_G(cursor).map_err(|_| FrostError::InvalidCommitment(l))? + ]) } #[allow(non_snake_case)] struct Package { B: HashMap>, C::F)>, Rs: Vec>, - share: Vec + share: C::F, } // Has every signer perform the role of the signature aggregator // Step 1 was already deprecated by performing nonce generation as needed // Step 2 is simply the broadcast round from step 1 -fn sign_with_share>( +fn sign_with_share>( params: &mut Params, our_preprocess: PreprocessPackage, - mut commitments: HashMap>, + mut commitments: HashMap, msg: &[u8], ) -> Result<(Package, Vec), FrostError> { let multisig_params = params.multisig_params(); - validate_map( - &mut commitments, - ¶ms.view.included, - (multisig_params.i, our_preprocess.serialized) - )?; + validate_map(&mut commitments, ¶ms.view.included, multisig_params.i)?; { - let transcript = params.algorithm.transcript(); // Domain separate FROST - transcript.domain_separate(b"FROST"); + params.algorithm.transcript().domain_separate(b"FROST"); } + let nonces = params.algorithm.nonces(); #[allow(non_snake_case)] let mut B = HashMap::::with_capacity(params.view.included.len()); - - // Get the binding factors - let nonces = params.algorithm.nonces(); - let mut addendums = HashMap::new(); - { - let transcript = params.algorithm.transcript(); // Parse the commitments for l in ¶ms.view.included { - transcript.append_message(b"participant", &l.to_be_bytes()); - let serialized = commitments.remove(l).unwrap(); - - let mut read_commitment = |c, label| { - let commitment = &serialized[c .. (c + G_len::())]; - transcript.append_message(label, commitment); - G_from_slice::(commitment).map_err(|_| FrostError::InvalidCommitment(*l)) - }; + { + params.algorithm.transcript().append_message(b"participant", &l.to_be_bytes()); + } // While this doesn't note which nonce/basepoint this is for, those are expected to be // static. Beyond that, they're committed to in the DLEq proof transcripts, ensuring // consistency. While this is suboptimal, it maintains IETF compliance, and Algorithm is // documented accordingly - #[allow(non_snake_case)] - let mut read_D_E = |c| Ok([ - read_commitment(c, b"commitment_D")?, - read_commitment(c + G_len::(), b"commitment_E")? - ]); + let transcript = |t: &mut A::Transcript, commitments: [C::G; 2]| { + t.append_message(b"commitment_D", commitments[0].to_bytes().as_ref()); + t.append_message(b"commitment_E", commitments[1].to_bytes().as_ref()); + }; - let mut c = 0; - let mut commitments = Vec::with_capacity(nonces.len()); - for (n, nonce_generators) in nonces.clone().iter_mut().enumerate() { - commitments.push(Vec::with_capacity(nonce_generators.len())); - - let first = nonce_generators.remove(0); - commitments[n].push(read_D_E(c)?); - c += 2 * G_len::(); - - let mut c = 2 * G_len::(); - for generator in nonce_generators { - commitments[n].push(read_D_E(c)?); - c += 2 * G_len::(); - for de in 0 .. 2 { - DLEqProof::deserialize( - &mut std::io::Cursor::new(&serialized[c .. (c + (2 * F_len::()))]) - ).map_err(|_| FrostError::InvalidCommitment(*l))?.verify( - &mut nonce_transcript::(), - Generators::new(first, *generator), - (commitments[n][0][de], commitments[n][commitments[n].len() - 1][de]) - ).map_err(|_| FrostError::InvalidCommitment(*l))?; - c += 2 * F_len::(); + if *l == params.keys.params.i { + for nonce_commitments in &our_preprocess.commitments { + for commitments in nonce_commitments { + transcript(params.algorithm.transcript(), *commitments); } } - addendums.insert(*l, serialized[c ..].to_vec()); + B.insert(*l, (our_preprocess.commitments.clone(), C::F::zero())); + params.algorithm.process_addendum( + ¶ms.view, + *l, + &mut Cursor::new(our_preprocess.addendum.clone()) + )?; + } else { + let mut cursor = commitments.remove(l).unwrap(); + + let mut commitments = Vec::with_capacity(nonces.len()); + for (n, nonce_generators) in nonces.clone().iter_mut().enumerate() { + commitments.push(Vec::with_capacity(nonce_generators.len())); + + let first = nonce_generators.remove(0); + commitments[n].push(read_D_E::<_, C>(&mut cursor, *l)?); + transcript(params.algorithm.transcript(), commitments[n][0]); + + for generator in nonce_generators { + commitments[n].push(read_D_E::<_, C>(&mut cursor, *l)?); + transcript(params.algorithm.transcript(), commitments[n][commitments[n].len() - 1]); + for de in 0 .. 2 { + DLEqProof::deserialize( + &mut cursor + ).map_err(|_| FrostError::InvalidCommitment(*l))?.verify( + &mut nonce_transcript::(), + Generators::new(first, *generator), + (commitments[n][0][de], commitments[n][commitments[n].len() - 1][de]) + ).map_err(|_| FrostError::InvalidCommitment(*l))?; + } + } + } + + B.insert(*l, (commitments, C::F::zero())); + params.algorithm.process_addendum(¶ms.view, *l, &mut cursor)?; } - B.insert(*l, (commitments, C::F::zero())); } // Re-format into the FROST-expected rho transcript @@ -225,7 +235,7 @@ fn sign_with_share>( // protocol rho_transcript.append_message( b"commitments", - &C::hash_msg(transcript.challenge(b"commitments").as_ref()) + &C::hash_msg(params.algorithm.transcript().challenge(b"commitments").as_ref()) ); // Include the offset, if one exists // While this isn't part of the FROST-expected rho transcript, the offset being here coincides @@ -243,12 +253,10 @@ fn sign_with_share>( // Merge the rho transcript back into the global one to ensure its advanced while committing to // everything - transcript.append_message(b"rho_transcript", rho_transcript.challenge(b"merge").as_ref()); - } - - // Process the addendums - for l in ¶ms.view.included { - params.algorithm.process_addendum(¶ms.view, *l, &addendums[l])?; + params.algorithm.transcript().append_message( + b"rho_transcript", + rho_transcript.challenge(b"merge").as_ref() + ); } #[allow(non_snake_case)] @@ -275,23 +283,26 @@ fn sign_with_share>( |nonces| nonces[0] + (nonces[1] * B[¶ms.keys.params.i()].1) ).collect::>(), msg - ).to_repr().as_ref().to_vec(); - - Ok((Package { B, Rs, share: share.clone() }, share)) + ); + Ok((Package { B, Rs, share }, share.to_repr().as_ref().to_vec())) } -fn complete>( +fn complete>( sign_params: &Params, sign: Package, - mut shares: HashMap>, + mut shares: HashMap, ) -> Result { let params = sign_params.multisig_params(); - validate_map(&mut shares, &sign_params.view.included, (params.i(), sign.share))?; + validate_map(&mut shares, &sign_params.view.included, params.i)?; let mut responses = HashMap::new(); let mut sum = C::F::zero(); for l in &sign_params.view.included { - let part = F_from_slice::(&shares[l]).map_err(|_| FrostError::InvalidShare(*l))?; + let part = if *l == params.i { + sign.share + } else { + C::read_F(shares.get_mut(l).unwrap()).map_err(|_| FrostError::InvalidShare(*l))? + }; sum += part; responses.insert(*l, part); } @@ -322,9 +333,7 @@ fn complete>( // If everyone has a valid share and there were enough participants, this should've worked Err( - FrostError::InternalError( - "everyone had a valid share yet the signature was still invalid".to_string() - ) + FrostError::InternalError("everyone had a valid share yet the signature was still invalid") ) } @@ -349,9 +358,9 @@ pub trait SignMachine { /// index = Vec index. None is expected at index 0 to allow for this. None is also expected at /// index i which is locally handled. Returns a byte vector representing a share of the signature /// for every other participant to receive, over an authenticated channel - fn sign( + fn sign( self, - commitments: HashMap>, + commitments: HashMap, msg: &[u8], ) -> Result<(Self::SignatureMachine, Vec), FrostError>; } @@ -361,7 +370,7 @@ pub trait SignatureMachine { /// Takes in everyone elses' shares submitted to us as a Vec, expecting participant index = /// Vec index with None at index 0 and index i. Returns a byte vector representing the serialized /// signature - fn complete(self, shares: HashMap>) -> Result; + fn complete(self, shares: HashMap) -> Result; } /// State machine which manages signing for an arbitrary signature algorithm @@ -392,9 +401,8 @@ impl> AlgorithmMachine { pub(crate) fn unsafe_override_preprocess( self, preprocess: PreprocessPackage - ) -> (AlgorithmSignMachine, Vec) { - let serialized = preprocess.serialized.clone(); - (AlgorithmSignMachine { params: self.params, preprocess }, serialized) + ) -> AlgorithmSignMachine { + AlgorithmSignMachine { params: self.params, preprocess } } } @@ -407,8 +415,7 @@ impl> PreprocessMachine for AlgorithmMachine { rng: &mut R ) -> (Self::SignMachine, Vec) { let mut params = self.params; - let preprocess = preprocess::(rng, &mut params); - let serialized = preprocess.serialized.clone(); + let (preprocess, serialized) = preprocess::(rng, &mut params); (AlgorithmSignMachine { params, preprocess }, serialized) } } @@ -416,9 +423,9 @@ impl> PreprocessMachine for AlgorithmMachine { impl> SignMachine for AlgorithmSignMachine { type SignatureMachine = AlgorithmSignatureMachine; - fn sign( + fn sign( self, - commitments: HashMap>, + commitments: HashMap, msg: &[u8] ) -> Result<(Self::SignatureMachine, Vec), FrostError> { let mut params = self.params; @@ -431,7 +438,7 @@ impl< C: Curve, A: Algorithm > SignatureMachine for AlgorithmSignatureMachine { - fn complete(self, shares: HashMap>) -> Result { + fn complete(self, shares: HashMap) -> Result { complete(&self.params, self.sign, shares) } } diff --git a/crypto/frost/src/tests/curve.rs b/crypto/frost/src/tests/curve.rs index 092ee50f..fc825273 100644 --- a/crypto/frost/src/tests/curve.rs +++ b/crypto/frost/src/tests/curve.rs @@ -1,3 +1,5 @@ +use std::io::Cursor; + use rand_core::{RngCore, CryptoRng}; use group::{ff::Field, Group}; @@ -13,7 +15,7 @@ fn key_generation(rng: &mut R) { // Test serialization of generated keys fn keys_serialization(rng: &mut R) { for (_, keys) in key_gen::<_, C>(rng) { - assert_eq!(&FrostKeys::::deserialize(&keys.serialize()).unwrap(), &*keys); + assert_eq!(&FrostKeys::::deserialize(&mut Cursor::new(keys.serialize())).unwrap(), &*keys); } } diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index 3c982cbf..ba0aa54a 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, collections::HashMap}; +use std::{io::Cursor, sync::Arc, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; @@ -46,15 +46,13 @@ pub fn key_gen( ); let (machine, these_commitments) = machine.generate_coefficients(rng); machines.insert(i, machine); - commitments.insert(i, these_commitments); + commitments.insert(i, Cursor::new(these_commitments)); } let mut secret_shares = HashMap::new(); let mut machines = machines.drain().map(|(l, machine)| { let (machine, shares) = machine.generate_secret_shares( rng, - // clone_without isn't necessary, as this machine's own data will be inserted without - // conflict, yet using it ensures the machine's own data is actually inserted as expected clone_without(&commitments, &l) ).unwrap(); secret_shares.insert(l, shares); @@ -69,7 +67,7 @@ pub fn key_gen( if i == *l { continue; } - our_secret_shares.insert(*l, shares[&i].clone()); + our_secret_shares.insert(*l, Cursor::new(shares[&i].clone())); } let these_keys = machine.complete(rng, our_secret_shares).unwrap(); @@ -140,14 +138,14 @@ pub fn sign( let mut commitments = HashMap::new(); let mut machines = machines.drain().map(|(i, machine)| { let (machine, preprocess) = machine.preprocess(rng); - commitments.insert(i, preprocess); + commitments.insert(i, Cursor::new(preprocess)); (i, machine) }).collect::>(); let mut shares = HashMap::new(); let mut machines = machines.drain().map(|(i, machine)| { let (machine, share) = machine.sign(clone_without(&commitments, &i), msg).unwrap(); - shares.insert(i, share); + shares.insert(i, Cursor::new(share)); (i, machine) }).collect::>(); diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index 7fc2458c..86e06d63 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -1,14 +1,14 @@ -use std::{sync::Arc, collections::HashMap}; +use std::{io::Cursor, sync::Arc, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; use group::{ff::PrimeField, GroupEncoding}; use crate::{ - curve::{Curve, F_from_slice, G_from_slice}, FrostKeys, + curve::Curve, FrostKeys, algorithm::{Schnorr, Hram}, sign::{PreprocessPackage, SignMachine, SignatureMachine, AlgorithmMachine}, - tests::{curve::test_curve, schnorr::test_schnorr, recover} + tests::{clone_without, curve::test_curve, schnorr::test_schnorr, recover} }; pub struct Vectors { @@ -27,7 +27,7 @@ pub struct Vectors { // Load these vectors into FrostKeys using a custom serialization it'll deserialize fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap> { let shares = vectors.shares.iter().map( - |secret| F_from_slice::(&hex::decode(secret).unwrap()).unwrap() + |secret| C::read_F(&mut Cursor::new(hex::decode(secret).unwrap())).unwrap() ).collect::>(); let verification_shares = shares.iter().map( |secret| C::GENERATOR * secret @@ -36,7 +36,7 @@ fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap(vectors: &Vectors) -> HashMap::deserialize(&serialized).unwrap(); + let these_keys = FrostKeys::::deserialize(&mut Cursor::new(serialized)).unwrap(); assert_eq!(these_keys.params().t(), vectors.threshold); assert_eq!(usize::from(these_keys.params().n()), shares.len()); assert_eq!(these_keys.params().i(), i); @@ -70,14 +70,14 @@ pub fn test_with_vectors< // Test against the vectors let keys = vectors_to_multisig_keys::(&vectors); - let group_key = G_from_slice::(&hex::decode(vectors.group_key).unwrap()).unwrap(); + let group_key = C::read_G(&mut Cursor::new(hex::decode(vectors.group_key).unwrap())).unwrap(); assert_eq!( - C::GENERATOR * F_from_slice::(&hex::decode(vectors.group_secret).unwrap()).unwrap(), + C::GENERATOR * C::read_F(&mut Cursor::new(hex::decode(vectors.group_secret).unwrap())).unwrap(), group_key ); assert_eq!( recover(&keys), - F_from_slice::(&hex::decode(vectors.group_secret).unwrap()).unwrap() + C::read_F(&mut Cursor::new(hex::decode(vectors.group_secret).unwrap())).unwrap() ); let mut machines = vec![]; @@ -96,19 +96,28 @@ pub fn test_with_vectors< let mut c = 0; let mut machines = machines.drain(..).map(|(i, machine)| { let nonces = [ - F_from_slice::(&hex::decode(vectors.nonces[c][0]).unwrap()).unwrap(), - F_from_slice::(&hex::decode(vectors.nonces[c][1]).unwrap()).unwrap() + C::read_F(&mut Cursor::new(hex::decode(vectors.nonces[c][0]).unwrap())).unwrap(), + C::read_F(&mut Cursor::new(hex::decode(vectors.nonces[c][1]).unwrap())).unwrap() ]; c += 1; - - let mut serialized = (C::GENERATOR * nonces[0]).to_bytes().as_ref().to_vec(); - serialized.extend((C::GENERATOR * nonces[1]).to_bytes().as_ref()); - - let (machine, serialized) = machine.unsafe_override_preprocess( - PreprocessPackage { nonces: vec![nonces], serialized: serialized.clone() } + let these_commitments = vec![[C::GENERATOR * nonces[0], C::GENERATOR * nonces[1]]]; + let machine = machine.unsafe_override_preprocess( + PreprocessPackage { + nonces: vec![nonces], + commitments: vec![these_commitments.clone()], + addendum: vec![] + } ); - commitments.insert(i, serialized); + commitments.insert( + i, + Cursor::new( + [ + these_commitments[0][0].to_bytes().as_ref(), + these_commitments[0][1].to_bytes().as_ref() + ].concat().to_vec() + ) + ); (i, machine) }).collect::>(); @@ -116,19 +125,19 @@ pub fn test_with_vectors< c = 0; let mut machines = machines.drain(..).map(|(i, machine)| { let (machine, share) = machine.sign( - commitments.clone(), + clone_without(&commitments, &i), &hex::decode(vectors.msg).unwrap() ).unwrap(); assert_eq!(share, hex::decode(vectors.sig_shares[c]).unwrap()); c += 1; - shares.insert(i, share); + shares.insert(i, Cursor::new(share)); (i, machine) }).collect::>(); - for (_, machine) in machines.drain() { - let sig = machine.complete(shares.clone()).unwrap(); + for (i, machine) in machines.drain() { + let sig = machine.complete(clone_without(&shares, &i)).unwrap(); let mut serialized = sig.R.to_bytes().as_ref().to_vec(); serialized.extend(sig.s.to_repr().as_ref()); assert_eq!(hex::encode(serialized), vectors.sig); From a1599df126003db619649828dcc971f31ab2d1c6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 13 Jul 2022 02:48:11 -0400 Subject: [PATCH 7/8] Update the processor for the previous commit --- processor/src/lib.rs | 4 ++-- processor/src/tests/mod.rs | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/processor/src/lib.rs b/processor/src/lib.rs index fe427dfe..ee7ad1fa 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -1,4 +1,4 @@ -use std::{marker::Send, collections::HashMap}; +use std::{marker::Send, io::Cursor, collections::HashMap}; use async_trait::async_trait; use thiserror::Error; @@ -17,7 +17,7 @@ pub enum NetworkError {} #[async_trait] pub trait Network: Send { - async fn round(&mut self, data: Vec) -> Result>, NetworkError>; + async fn round(&mut self, data: Vec) -> Result>>, NetworkError>; } #[derive(Clone, Error, Debug)] diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index 051ecad6..89bf069f 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -1,4 +1,4 @@ -use std::{sync::{Arc, RwLock}, collections::HashMap}; +use std::{io::Cursor, sync::{Arc, RwLock}, collections::HashMap}; use async_trait::async_trait; @@ -11,7 +11,7 @@ struct LocalNetwork { i: u16, size: u16, round: usize, - rounds: Arc>>>> + rounds: Arc>>>>> } impl LocalNetwork { @@ -27,13 +27,13 @@ impl LocalNetwork { #[async_trait] impl Network for LocalNetwork { - async fn round(&mut self, data: Vec) -> Result>, NetworkError> { + async fn round(&mut self, data: Vec) -> Result>>, NetworkError> { { let mut rounds = self.rounds.write().unwrap(); if rounds.len() == self.round { rounds.push(HashMap::new()); } - rounds[self.round].insert(self.i, data); + rounds[self.round].insert(self.i, Cursor::new(data)); } while { @@ -43,7 +43,8 @@ impl Network for LocalNetwork { tokio::task::yield_now().await; } - let res = self.rounds.try_read().unwrap()[self.round].clone(); + let mut res = self.rounds.try_read().unwrap()[self.round].clone(); + res.remove(&self.i); self.round += 1; Ok(res) } From 95a4101a9abf78b91463836e48c9a00ad08b62a4 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 13 Jul 2022 02:48:36 -0400 Subject: [PATCH 8/8] Tweak the hash_to_point test It ran for too long and had a "test_" prefix not shared with other tests. --- coins/monero/src/tests/hash_to_point.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coins/monero/src/tests/hash_to_point.rs b/coins/monero/src/tests/hash_to_point.rs index b04bbb9f..207f2ee5 100644 --- a/coins/monero/src/tests/hash_to_point.rs +++ b/coins/monero/src/tests/hash_to_point.rs @@ -2,12 +2,12 @@ use rand::rngs::OsRng; use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; -use crate::{random_scalar, ringct::hash_to_point::{hash_to_point, rust_hash_to_point}}; +use crate::{random_scalar, ringct::hash_to_point::{hash_to_point as c_hash_to_point, rust_hash_to_point}}; #[test] -fn test_hash_to_point() { - for _ in 0 .. 200 { +fn hash_to_point() { + for _ in 0 .. 50 { let point = &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE; - assert_eq!(rust_hash_to_point(point), hash_to_point(point)); + assert_eq!(rust_hash_to_point(point), c_hash_to_point(point)); } }