diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index dfe24ad6..0da52dc0 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -39,14 +39,14 @@ impl Curve for Ed25519 { type G = dfg::EdwardsPoint; type T = &'static dfg::EdwardsBasepointTable; - fn id() -> String { - "Ed25519".to_string() - } - fn id_len() -> u8 { u8::try_from(Self::id().len()).unwrap() } + fn id() -> &'static [u8] { + b"Ed25519" + } + fn generator() -> Self::G { Self::G::generator() } @@ -67,11 +67,11 @@ impl Curve for Ed25519 { } fn hash_binding_factor(binding: &[u8]) -> Self::F { - Self::hash_to_F(&[b"rho", binding].concat()) + Self::hash_to_F(b"rho", binding) } - fn hash_to_F(data: &[u8]) -> Self::F { - dfg::Scalar::from_hash(Blake2b512::new().chain(data)) + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { + dfg::Scalar::from_hash(Blake2b512::new().chain(dst).chain(msg)) } fn F_len() -> usize { diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index e0ae0b85..d5f5f2dc 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" thiserror = "1" rand_core = "0.6" +hex = "0.4" ff = "0.11" group = "0.11" @@ -21,4 +22,4 @@ multiexp = { path = "../multiexp", features = ["batch"] } [dev-dependencies] rand = "0.8" sha2 = "0.10" -k256 = { version = "0.10", features = ["arithmetic"] } +p256 = { version = "0.10", features = ["arithmetic"] } diff --git a/crypto/frost/src/key_gen.rs b/crypto/frost/src/key_gen.rs index 67e32a76..643a2454 100644 --- a/crypto/frost/src/key_gen.rs +++ b/crypto/frost/src/key_gen.rs @@ -16,8 +16,13 @@ use crate::{ #[allow(non_snake_case)] fn challenge(context: &str, l: u16, R: &[u8], Am: &[u8]) -> C::F { const DST: &'static [u8] = b"FROST Schnorr Proof of Knowledge"; + // Uses hash_msg to get a fixed size value out of the context string - C::hash_to_F(&[DST, &C::hash_msg(context.as_bytes()), &l.to_be_bytes(), R, Am].concat()) + let mut transcript = C::hash_msg(context.as_bytes()); + transcript.extend(l.to_be_bytes()); + transcript.extend(R); + transcript.extend(Am); + C::hash_to_F(DST, &transcript) } // Implements steps 1 through 3 of round 1 of FROST DKG. Returns the coefficients, commitments, and diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 35071889..a600466a 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -40,12 +40,11 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug { /// Precomputed table type type T: Mul; - /// ID for this curve - fn id() -> String; /// Byte length of the curve ID - // While curve.id().len() is trivial, this bounds it to u8 and lets us ignore the possibility it - // contains Unicode, therefore having a String length which is different from its byte length + // 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]; /// Generator for the group // While group does provide this in its API, Jubjub users will want to use a custom basepoint @@ -79,7 +78,7 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug { // Not parameterized by Digest as it's fine for it to use its own hash function as relevant to // hash_msg and hash_binding_factor #[allow(non_snake_case)] - fn hash_to_F(data: &[u8]) -> Self::F; + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; /// Constant size of a serialized field element // The alternative way to grab this would be either serializing a junk element and getting its @@ -255,6 +254,10 @@ pub struct MultisigKeys { } impl MultisigKeys { + /// Offset the keys by a given scalar to allow for account and privacy schemes + /// This offset is ephemeral and will not be included when these keys are serialized + /// Keys offset multiple times will form a new offset of their sum + /// Not IETF compliant pub fn offset(&self, offset: C::F) -> MultisigKeys { let mut res = self.clone(); // Carry any existing offset @@ -311,7 +314,7 @@ impl MultisigKeys { 1 + usize::from(C::id_len()) + MultisigKeys::::serialized_len(self.params.n) ); serialized.push(C::id_len()); - serialized.extend(C::id().as_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()); @@ -336,8 +339,7 @@ impl MultisigKeys { Err(FrostError::InternalError("ID wasn't included".to_string()))?; } - let id = &serialized[cursor .. (cursor + id_len)]; - if C::id().as_bytes() != id { + if C::id() != &serialized[cursor .. (cursor + id_len)] { Err( FrostError::InternalError( "curve is distinct between serialization and deserialization".to_string() diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 23a63f50..ae33735b 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -69,9 +69,9 @@ impl> Params { } } -struct PreprocessPackage { - nonces: [C::F; 2], - serialized: Vec, +pub(crate) struct PreprocessPackage { + pub(crate) nonces: [C::F; 2], + pub(crate) serialized: Vec, } // This library unifies the preprocessing step with signing due to security concerns and to provide @@ -306,6 +306,16 @@ impl> AlgorithmMachine { } ) } + + pub(crate) fn unsafe_override_preprocess(&mut self, preprocess: PreprocessPackage) { + if self.state != State::Fresh { + // This would be unacceptable, yet this is pub(crate) and explicitly labelled unsafe + // It's solely used in a testing environment, which is how it's justified + Err::<(), _>(FrostError::InvalidSignTransition(State::Fresh, self.state)).unwrap(); + } + self.preprocess = Some(preprocess); + self.state = State::Preprocessed; + } } impl> StateMachine for AlgorithmMachine { diff --git a/crypto/frost/src/tests/literal/mod.rs b/crypto/frost/src/tests/literal/mod.rs index d766f844..adb87b1a 100644 --- a/crypto/frost/src/tests/literal/mod.rs +++ b/crypto/frost/src/tests/literal/mod.rs @@ -1,2 +1,2 @@ -mod secp256k1; +mod p256; mod schnorr; diff --git a/crypto/frost/src/tests/literal/p256.rs b/crypto/frost/src/tests/literal/p256.rs new file mode 100644 index 00000000..75aa07bb --- /dev/null +++ b/crypto/frost/src/tests/literal/p256.rs @@ -0,0 +1,222 @@ +use core::convert::TryInto; + +use rand::rngs::OsRng; + +use ff::{Field, PrimeField}; +use group::GroupEncoding; + +use sha2::{digest::Update, Digest, Sha256}; + +use p256::{elliptic_curve::bigint::{Encoding, U384}, Scalar, ProjectivePoint}; + +use crate::{ + CurveError, Curve, + algorithm::Hram, + tests::{curve::test_curve, vectors::{Vectors, vectors}} +}; + +const CONTEXT_STRING: &[u8] = b"FROST-P256-SHA256-v5"; + +fn expand_message_xmd_sha256(dst: &[u8], msg: &[u8], len: u16) -> Option> { + const OUTPUT_SIZE: u16 = 32; + const BLOCK_SIZE: u16 = 64; + + let blocks = ((len + OUTPUT_SIZE) - 1) / OUTPUT_SIZE; + if blocks > 255 { + return None; + } + let blocks = blocks as u8; + + let mut dst = dst; + let oversize = Sha256::digest([b"H2C-OVERSIZE-DST-", dst].concat()); + if dst.len() > 255 { + dst = &oversize; + } + let dst_prime = &[dst, &[dst.len() as u8]].concat(); + + let mut msg_prime = vec![0; BLOCK_SIZE.into()]; + msg_prime.extend(msg); + msg_prime.extend(len.to_be_bytes()); + msg_prime.push(0); + msg_prime.extend(dst_prime); + + let mut b = vec![Sha256::digest(&msg_prime).to_vec()]; + + { + let mut b1 = b[0].clone(); + b1.push(1); + b1.extend(dst_prime); + b.push(Sha256::digest(&b1).to_vec()); + } + + for i in 2 ..= blocks { + let mut msg = b[0] + .iter().zip(b[usize::from(i) - 1].iter()) + .map(|(a, b)| *a ^ b).collect::>(); + msg.push(i); + msg.extend(dst_prime); + b.push(Sha256::digest(msg).to_vec()); + } + + Some(b[1 ..].concat()[.. usize::from(len)].to_vec()) +} + +#[test] +fn test_xmd_sha256() { + assert_eq!( + hex::encode(expand_message_xmd_sha256(b"QUUX-V01-CS02-with-expander", b"", 0x80).unwrap()), + ( + "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8".to_owned() + + "9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991" + + "e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02" + + "fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608" + + "61c0cde2005afc2c114042ee7b5848f5303f0611cf297f" + ) + ); +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct P256; +impl Curve for P256 { + type F = Scalar; + type G = ProjectivePoint; + type T = ProjectivePoint; + + fn id_len() -> u8 { + u8::try_from(Self::id().len()).unwrap() + } + + fn id() -> &'static [u8] { + b"P-256" + } + + fn generator() -> Self::G { + Self::G::GENERATOR + } + + fn generator_table() -> Self::T { + Self::G::GENERATOR + } + + fn little_endian() -> bool { + false + } + + fn hash_msg(msg: &[u8]) -> Vec { + (&Sha256::new() + .chain(CONTEXT_STRING) + .chain(b"digest") + .chain(msg) + .finalize() + ).to_vec() + } + + fn hash_binding_factor(binding: &[u8]) -> Self::F { + Self::hash_to_F(&[CONTEXT_STRING, b"rho"].concat(), binding) + } + + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { + let mut modulus = vec![0; 16]; + modulus.extend(&(Scalar::zero() - Scalar::one()).to_repr()); + let modulus = U384::from_be_slice(&modulus).wrapping_add(&U384::ONE); + Self::F_from_slice( + &U384::from_be_slice( + &expand_message_xmd_sha256(dst, msg, 48).unwrap() + ).reduce(&modulus).unwrap().to_be_bytes()[16 ..] + ).unwrap() + } + + fn F_len() -> usize { + 32 + } + + fn G_len() -> usize { + 33 + } + + fn F_from_slice(slice: &[u8]) -> Result { + let bytes: [u8; 32] = slice.try_into() + .map_err(|_| CurveError::InvalidLength(32, slice.len()))?; + + let scalar = Scalar::from_repr(bytes.into()); + if scalar.is_none().into() { + Err(CurveError::InvalidScalar)?; + } + + Ok(scalar.unwrap()) + } + + fn G_from_slice(slice: &[u8]) -> Result { + let bytes: [u8; 33] = slice.try_into() + .map_err(|_| CurveError::InvalidLength(33, slice.len()))?; + + let point = ProjectivePoint::from_bytes(&bytes.into()); + if point.is_none().into() { + Err(CurveError::InvalidPoint)?; + } + + Ok(point.unwrap()) + } + + fn F_to_bytes(f: &Self::F) -> Vec { + (&f.to_bytes()).to_vec() + } + + fn G_to_bytes(g: &Self::G) -> Vec { + (&g.to_bytes()).to_vec() + } +} + +#[test] +fn p256_curve() { + test_curve::<_, P256>(&mut OsRng); +} + +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct IetfP256Hram {} +impl Hram for IetfP256Hram { + #[allow(non_snake_case)] + fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar { + P256::hash_to_F( + &[CONTEXT_STRING, b"chal"].concat(), + &[&P256::G_to_bytes(R), &P256::G_to_bytes(A), m].concat() + ) + } +} + +#[test] +fn p256_vectors() { + vectors::( + Vectors { + threshold: 2, + shares: &[ + "0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731", + "8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84067159c52e1863ad5", + "0e80d6e8f6192c003b5488ce1eec8f5429587d48cf001541e713b2d53c09d928" + ], + group_secret: "8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735fa48b301bd8de", + group_key: "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70", + + msg: "74657374", + included: &[1, 3], + nonces: &[ + [ + "081617b24375e069b39f649d4c4ce2fba6e38b73e7c16759de0b6079a22c4c7e", + "4de5fb77d99f03a2491a83a6a4cb91ca3c82a3f34ce94cec939174f47c9f95dd" + ], + [ + "d186ea92593f83ea83181b184d41aa93493301ac2bc5b4b1767e94d2db943e38", + "486e2ee25a3fbc8e6399d748b077a2755fde99fa85cc24fa647ea4ebf5811a15" + ] + ], + binding: "cf7ffe4b8ad6edb6237efaa8cbfb2dfb2fd08d163b6ad9063720f14779a9e143", + sig_shares: &[ + "9e4d8865faf8c7b3193a3b35eda3d9e12118447114b1e7d5b4809ea28067f8a9", + "b7d094eab6305ae74daeed1acd31abba9ab81f638d38b72c132cb25a5dfae1fc" + ], + sig: "0342c14c77f9d4ef9b8bd64fb0d7bbfdb9f8216a44e5f7bbe6ac0f3ed5e1a57367".to_owned() + + "561e1d51b129229966e92850bad5859bfee96926fad3007cd3f38639e1ffb554" + } + ); +} diff --git a/crypto/frost/src/tests/literal/schnorr.rs b/crypto/frost/src/tests/literal/schnorr.rs index 1ceac748..0d81bf8a 100644 --- a/crypto/frost/src/tests/literal/schnorr.rs +++ b/crypto/frost/src/tests/literal/schnorr.rs @@ -4,7 +4,7 @@ use rand::rngs::OsRng; use crate::{ Curve, schnorr, algorithm::{Hram, Schnorr}, - tests::{key_gen, algorithm_machines, sign as sign_test, literal::secp256k1::{Secp256k1, TestHram}} + tests::{key_gen, algorithm_machines, sign as sign_test, literal::p256::{P256, IetfP256Hram}} }; const MESSAGE: &[u8] = b"Hello World"; @@ -15,8 +15,8 @@ fn sign() { &mut OsRng, algorithm_machines( &mut OsRng, - Schnorr::::new(), - &key_gen::<_, Secp256k1>(&mut OsRng) + Schnorr::::new(), + &key_gen::<_, P256>(&mut OsRng) ), MESSAGE ); @@ -24,19 +24,19 @@ fn sign() { #[test] fn sign_with_offset() { - let mut keys = key_gen::<_, Secp256k1>(&mut OsRng); + let mut keys = key_gen::<_, P256>(&mut OsRng); let group_key = keys[&1].group_key(); - let offset = Secp256k1::hash_to_F(b"offset"); + let offset = P256::hash_to_F(b"offset", &[]); for i in 1 ..= u16::try_from(keys.len()).unwrap() { keys.insert(i, Rc::new(keys[&i].offset(offset))); } - let offset_key = group_key + (Secp256k1::generator_table() * offset); + let offset_key = group_key + (P256::generator_table() * offset); let sig = sign_test( &mut OsRng, - algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), + algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), MESSAGE ); - assert!(schnorr::verify(offset_key, TestHram::hram(&sig.R, &offset_key, MESSAGE), &sig)); + assert!(schnorr::verify(offset_key, IetfP256Hram::hram(&sig.R, &offset_key, MESSAGE), &sig)); } diff --git a/crypto/frost/src/tests/literal/secp256k1.rs b/crypto/frost/src/tests/literal/secp256k1.rs deleted file mode 100644 index e10bee07..00000000 --- a/crypto/frost/src/tests/literal/secp256k1.rs +++ /dev/null @@ -1,120 +0,0 @@ -use core::convert::TryInto; - -use rand::rngs::OsRng; - -use ff::PrimeField; -use group::GroupEncoding; - -use sha2::{Digest, Sha256, Sha512}; - -use k256::{ - elliptic_curve::{generic_array::GenericArray, bigint::{ArrayEncoding, U512}, ops::Reduce}, - Scalar, - ProjectivePoint -}; - -use crate::{CurveError, Curve, algorithm::Hram, tests::curve::test_curve}; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Secp256k1; -impl Curve for Secp256k1 { - type F = Scalar; - type G = ProjectivePoint; - type T = ProjectivePoint; - - fn id() -> String { - "secp256k1".to_string() - } - - fn id_len() -> u8 { - u8::try_from(Self::id().len()).unwrap() - } - - fn generator() -> Self::G { - Self::G::GENERATOR - } - - fn generator_table() -> Self::T { - Self::G::GENERATOR - } - - fn little_endian() -> bool { - false - } - - // The IETF draft doesn't specify a secp256k1 ciphersuite - // This test just uses the simplest ciphersuite which would still be viable to deploy - // The comparable P-256 curve uses hash_to_field from the Hash To Curve IETF draft with a context - // string and further DST for H1 ("rho") and H3 ("digest"). It's not currently worth it to add - // that weight, yet if secp256k1 is ever officially acknowledged (not just a testing curve), it - // must be properly implemented. - fn hash_msg(msg: &[u8]) -> Vec { - (&Sha256::digest(msg)).to_vec() - } - - fn hash_binding_factor(binding: &[u8]) -> Self::F { - Self::hash_to_F(&[b"rho", binding].concat()) - } - - // Use wide reduction for security - fn hash_to_F(data: &[u8]) -> Self::F { - Scalar::from_uint_reduced(U512::from_be_byte_array(Sha512::digest(data))) - } - - fn F_len() -> usize { - 32 - } - - fn G_len() -> usize { - 33 - } - - fn F_from_slice(slice: &[u8]) -> Result { - let bytes: [u8; 32] = slice.try_into() - .map_err(|_| CurveError::InvalidLength(32, slice.len()))?; - let scalar = Scalar::from_repr(bytes.into()); - if scalar.is_none().unwrap_u8() == 1 { - Err(CurveError::InvalidScalar)?; - } - Ok(scalar.unwrap()) - } - - fn G_from_slice(slice: &[u8]) -> Result { - let point = ProjectivePoint::from_bytes(GenericArray::from_slice(slice)); - if point.is_none().unwrap_u8() == 1 { - Err(CurveError::InvalidScalar)?; - } - Ok(point.unwrap()) - } - - fn F_to_bytes(f: &Self::F) -> Vec { - (&f.to_bytes()).to_vec() - } - - fn G_to_bytes(g: &Self::G) -> Vec { - (&g.to_bytes()).to_vec() - } -} - -#[allow(non_snake_case)] -#[derive(Clone)] -pub struct TestHram {} -impl Hram for TestHram { - #[allow(non_snake_case)] - fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar { - Scalar::from_uint_reduced( - U512::from_be_byte_array( - Sha512::new() - .chain_update(Secp256k1::G_to_bytes(R)) - .chain_update(Secp256k1::G_to_bytes(A)) - .chain_update(m) - .finalize() - ) - ) - } -} - -#[test] -fn secp256k1_curve() { - test_curve::<_, Secp256k1>(&mut OsRng); -} diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index f87ce812..cc9c8aad 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -18,6 +18,7 @@ mod schnorr; // Test suites for public usage pub mod curve; +pub mod vectors; // Literal test definitions to run during `cargo test` #[cfg(test)] diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs new file mode 100644 index 00000000..5c984aa3 --- /dev/null +++ b/crypto/frost/src/tests/vectors.rs @@ -0,0 +1,117 @@ +use std::{rc::Rc, collections::HashMap}; + +use crate::{ + Curve, MultisigKeys, + algorithm::{Schnorr, Hram}, + sign::{PreprocessPackage, StateMachine, AlgorithmMachine}, + tests::recover +}; + +pub struct Vectors { + pub threshold: u16, + pub shares: &'static [&'static str], + pub group_secret: &'static str, + pub group_key: &'static str, + + pub msg: &'static str, + pub included: &'static [u16], + pub nonces: &'static [[&'static str; 2]], + pub binding: &'static str, + pub sig_shares: &'static [&'static str], + pub sig: String +} + +// Load these vectors into MultisigKeys using a custom serialization it'll deserialize +fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap> { + let shares = vectors.shares.iter().map( + |secret| C::F_from_slice(&hex::decode(secret).unwrap()).unwrap() + ).collect::>(); + let verification_shares = shares.iter().map( + |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(vectors.threshold.to_be_bytes()); + serialized.extend(u16::try_from(shares.len()).unwrap().to_be_bytes()); + serialized.extend(i.to_be_bytes()); + serialized.extend(C::F_to_bytes(&shares[usize::from(i) - 1])); + serialized.extend(&hex::decode(vectors.group_key).unwrap()); + for share in &verification_shares { + serialized.extend(&C::G_to_bytes(share)); + } + + let these_keys = MultisigKeys::::deserialize(&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); + assert_eq!(these_keys.secret_share(), shares[usize::from(i - 1)]); + assert_eq!(&hex::encode(&C::G_to_bytes(&these_keys.group_key())), vectors.group_key); + keys.insert(i, these_keys); + } + + keys +} + +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(), + group_key + ); + assert_eq!( + recover(&keys), + C::F_from_slice(&hex::decode(vectors.group_secret).unwrap()).unwrap() + ); + + let mut machines = vec![]; + for i in vectors.included { + machines.push(( + *i, + AlgorithmMachine::new( + Schnorr::::new(), + Rc::new(keys[i].clone()), + vectors.included.clone() + ).unwrap() + )); + } + + let mut commitments = HashMap::new(); + let mut c = 0; + for (i, machine) in machines.iter_mut() { + let nonces = [ + C::F_from_slice(&hex::decode(vectors.nonces[c][0]).unwrap()).unwrap(), + 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]))); + + machine.unsafe_override_preprocess( + PreprocessPackage { nonces, serialized: serialized.clone() } + ); + + commitments.insert(*i, serialized); + c += 1; + } + + let mut shares = HashMap::new(); + c = 0; + for (i, machine) in machines.iter_mut() { + let share = machine.sign(commitments.clone(), &hex::decode(vectors.msg).unwrap()).unwrap(); + assert_eq!(share, hex::decode(vectors.sig_shares[c]).unwrap()); + shares.insert(*i, share); + c += 1; + } + + for (_, machine) in machines.iter_mut() { + let sig = machine.complete(shares.clone()).unwrap(); + let mut serialized = C::G_to_bytes(&sig.R); + serialized.extend(C::F_to_bytes(&sig.s)); + assert_eq!(hex::encode(serialized), vectors.sig); + } +}