Use GroupEncoding instead of Curve's from_slice/to_bytes

Increases usage of standardization while expanding dalek_ff_group.

Closes https://github.com/serai-dex/serai/issues/26 by moving 
dfg::EdwardsPoint to only be for the prime subgroup.
This commit is contained in:
Luke Parker 2022-06-28 01:25:26 -04:00
parent ac17645fc8
commit 3de7a76051
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
14 changed files with 141 additions and 178 deletions

View file

@ -3,6 +3,8 @@ use core::convert::TryInto;
use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use group::GroupEncoding;
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE as DTable,
scalar::Scalar as DScalar,
@ -10,7 +12,6 @@ use curve25519_dalek::{
};
use transcript::{Transcript, RecommendedTranscript};
use frost::curve::{Curve, Ed25519};
use dalek_ff_group as dfg;
use crate::random_scalar;
@ -118,18 +119,26 @@ impl DLEqProof {
}
#[allow(non_snake_case)]
pub fn read_dleq(
pub(crate) fn read_dleq(
serialized: &[u8],
start: usize,
H: &DPoint,
l: u16,
xG: &DPoint
) -> Result<dfg::EdwardsPoint, MultisigError> {
// Not using G_from_slice here would enable non-canonical points and break blame
// This does also ban identity points, yet those should never be a concern
let other = <Ed25519 as Curve>::G_from_slice(
&serialized[(start + 0) .. (start + 32)]
).map_err(|_| MultisigError::InvalidDLEqProof(l))?;
if serialized.len() < start + 96 {
Err(MultisigError::InvalidDLEqProof(l))?;
}
let bytes = (&serialized[(start + 0) .. (start + 32)]).try_into().unwrap();
// dfg ensures the point is torsion free
let other = Option::<dfg::EdwardsPoint>::from(
dfg::EdwardsPoint::from_bytes(&bytes)).ok_or(MultisigError::InvalidDLEqProof(l)
)?;
// Ensure this is a canonical point
if other.to_bytes() != bytes {
Err(MultisigError::InvalidDLEqProof(l))?;
}
DLEqProof::deserialize(&serialized[(start + 32) .. (start + 96)])
.ok_or(MultisigError::InvalidDLEqProof(l))?

View file

@ -226,6 +226,11 @@ impl SignMachine<Transaction> for TransactionSignMachine {
// FROST commitments, image, H commitments, and their proofs
let clsag_len = 64 + ClsagMultisig::serialized_len();
for (l, commitments) in &commitments {
if commitments.len() != (self.clsags.len() * clsag_len) {
Err(FrostError::InvalidCommitment(*l))?;
}
}
// Convert the unified commitments to a Vec of the individual commitments
let mut commitments = (0 .. self.clsags.len()).map(|_| commitments.iter_mut().map(

View file

@ -27,7 +27,7 @@ use dalek::{
}
};
use group::{ff::{Field, PrimeField}, Group};
use group::{ff::{Field, PrimeField}, Group, GroupEncoding, prime::PrimeGroup};
macro_rules! deref_borrow {
($Source: ident, $Target: ident) => {
@ -192,6 +192,7 @@ macro_rules! dalek_group {
(
$Point: ident,
$DPoint: ident,
$torsion_free: expr,
$Table: ident,
$DTable: ident,
@ -225,6 +226,29 @@ macro_rules! dalek_group {
fn double(&self) -> Self { *self + self }
}
impl GroupEncoding for $Point {
type Repr = [u8; 32];
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
if let Some(point) = $DCompressed(*bytes).decompress() {
if $torsion_free(point) {
return CtOption::new($Point(point), Choice::from(1));
}
}
CtOption::new($Point::identity(), Choice::from(0))
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
$Point::from_bytes(bytes)
}
fn to_bytes(&self) -> Self::Repr {
self.0.compress().to_bytes()
}
}
impl PrimeGroup for $Point {}
pub struct $Compressed(pub $DCompressed);
deref_borrow!($Compressed, $DCompressed);
impl $Compressed {
@ -261,6 +285,7 @@ macro_rules! dalek_group {
dalek_group!(
EdwardsPoint,
DEdwardsPoint,
|point: DEdwardsPoint| point.is_torsion_free(),
EdwardsBasepointTable,
DEdwardsBasepointTable,
@ -272,15 +297,10 @@ dalek_group!(
ED25519_BASEPOINT_TABLE
);
impl EdwardsPoint {
pub fn is_torsion_free(&self) -> bool {
self.0.is_torsion_free()
}
}
dalek_group!(
RistrettoPoint,
DRistrettoPoint,
|_| true,
RistrettoBasepointTable,
DRistrettoBasepointTable,

View file

@ -1,34 +1,27 @@
use core::convert::TryInto;
use rand_core::{RngCore, CryptoRng};
use sha2::{Digest, Sha512};
use group::{ff::PrimeField, Group};
use dalek_ff_group::Scalar;
use crate::{curve::{CurveError, Curve}, algorithm::Hram};
use crate::{curve::Curve, algorithm::Hram};
macro_rules! dalek_curve {
(
$Curve: ident,
$Hram: ident,
$Point: ident,
$Compressed: ident,
$Table: ident,
$POINT: ident,
$TABLE: ident,
$torsioned: expr,
$ID: literal,
$CONTEXT: literal,
$chal: literal,
$digest: literal,
) => {
use dalek_ff_group::{$Point, $Compressed, $Table, $POINT, $TABLE};
use dalek_ff_group::{$Point, $Table, $POINT, $TABLE};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct $Curve;
@ -75,43 +68,6 @@ macro_rules! dalek_curve {
fn G_len() -> usize {
32
}
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
let scalar = Self::F::from_repr(
slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
);
if !bool::from(scalar.is_some()) {
Err(CurveError::InvalidScalar)?;
}
Ok(scalar.unwrap())
}
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
let bytes = slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?;
let point = $Compressed::new(bytes).decompress().ok_or(CurveError::InvalidPoint)?;
// Ban identity
if point.is_identity().into() {
Err(CurveError::InvalidPoint)?;
}
// Ban torsioned points to meet the prime order group requirement
if $torsioned(point) {
Err(CurveError::InvalidPoint)?;
}
// Ban points which weren't canonically encoded
if point.compress().to_bytes() != bytes {
Err(CurveError::InvalidPoint)?;
}
Ok(point)
}
fn F_to_bytes(f: &Self::F) -> Vec<u8> {
f.to_repr().to_vec()
}
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
g.compress().to_bytes().to_vec()
}
}
#[derive(Copy, Clone)]
@ -130,11 +86,9 @@ dalek_curve!(
Ristretto,
IetfRistrettoHram,
RistrettoPoint,
CompressedRistretto,
RistrettoBasepointTable,
RISTRETTO_BASEPOINT_POINT,
RISTRETTO_BASEPOINT_TABLE,
|_| false,
b"ristretto",
b"FROST-RISTRETTO255-SHA512-v5",
b"chal",
@ -146,11 +100,9 @@ dalek_curve!(
Ed25519,
IetfEd25519Hram,
EdwardsPoint,
CompressedEdwardsY,
EdwardsBasepointTable,
ED25519_BASEPOINT_POINT,
ED25519_BASEPOINT_TABLE,
|point: EdwardsPoint| !bool::from(point.is_torsion_free()),
b"edwards25519",
b"",
b"",

View file

@ -1,14 +1,12 @@
use core::convert::TryInto;
use rand_core::{RngCore, CryptoRng};
use sha2::{digest::Update, Digest, Sha256};
use group::{ff::{Field, PrimeField}, Group, GroupEncoding};
use group::{ff::Field, GroupEncoding};
use elliptic_curve::{bigint::{Encoding, U384}, hash2curve::{Expander, ExpandMsg, ExpandMsgXmd}};
use crate::{curve::{CurveError, Curve}, algorithm::Hram};
use crate::{curve::{Curve, F_from_slice}, algorithm::Hram};
macro_rules! kp_curve {
(
@ -65,7 +63,7 @@ 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);
Self::F_from_slice(
F_from_slice::<Self::F>(
&U384::from_be_slice(&{
let mut bytes = [0; 48];
ExpandMsgXmd::<Sha256>::expand_message(
@ -85,38 +83,6 @@ macro_rules! kp_curve {
fn G_len() -> usize {
33
}
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
let bytes: [u8; 32] = slice.try_into()
.map_err(|_| CurveError::InvalidLength(32, slice.len()))?;
let scalar = Self::F::from_repr(bytes.into());
if scalar.is_none().into() {
Err(CurveError::InvalidScalar)?;
}
Ok(scalar.unwrap())
}
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
let bytes: [u8; 33] = slice.try_into()
.map_err(|_| CurveError::InvalidLength(33, slice.len()))?;
let point = Self::G::from_bytes(&bytes.into());
if point.is_none().into() || point.unwrap().is_identity().into() {
Err(CurveError::InvalidPoint)?;
}
Ok(point.unwrap())
}
fn F_to_bytes(f: &Self::F) -> Vec<u8> {
f.to_bytes().to_vec()
}
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
g.to_bytes().to_vec()
}
}
#[derive(Clone)]
@ -126,7 +92,7 @@ macro_rules! kp_curve {
fn hram(R: &$lib::ProjectivePoint, A: &$lib::ProjectivePoint, m: &[u8]) -> $lib::Scalar {
$Curve::hash_to_F(
&[$CONTEXT as &[u8], b"chal"].concat(),
&[&$Curve::G_to_bytes(R), &$Curve::G_to_bytes(A), m].concat()
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat()
)
}
}

View file

@ -4,7 +4,7 @@ use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use group::{ff::PrimeField, Group, GroupOps};
use group::{ff::PrimeField, Group, GroupOps, prime::PrimeGroup};
#[cfg(any(test, feature = "dalek"))]
mod dalek;
@ -42,7 +42,7 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
// This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses
type F: PrimeField;
/// Group element type
type G: Group<Scalar = Self::F> + GroupOps;
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup;
/// Precomputed table type
type T: Mul<Self::F, Output = Self::G>;
@ -99,23 +99,31 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
// that is on them
#[allow(non_snake_case)]
fn G_len() -> usize;
/// Field element from slice. Preferred to be canonical yet does not have to be
// Required due to the lack of standardized encoding functions provided by ff/group
// While they do technically exist, their usage of Self::Repr breaks all potential library usage
// without helper functions like this
#[allow(non_snake_case)]
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError>;
/// Group element from slice. Must require canonicity or risks differing binding factors
#[allow(non_snake_case)]
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError>;
/// Obtain a vector of the byte encoding of F
#[allow(non_snake_case)]
fn F_to_bytes(f: &Self::F) -> Vec<u8>;
/// Obtain a vector of the byte encoding of G
#[allow(non_snake_case)]
fn G_to_bytes(g: &Self::G) -> Vec<u8>;
}
/// Field element from slice
#[allow(non_snake_case)]
pub(crate) fn F_from_slice<F: PrimeField>(slice: &[u8]) -> Result<F, CurveError> {
let mut encoding = F::Repr::default();
encoding.as_mut().copy_from_slice(slice);
let point = Option::<F>::from(F::from_repr(encoding)).ok_or(CurveError::InvalidScalar)?;
if point.to_repr().as_ref() != slice {
Err(CurveError::InvalidScalar)?;
}
Ok(point)
}
/// Group element from slice
#[allow(non_snake_case)]
pub(crate) fn G_from_slice<G: PrimeGroup>(slice: &[u8]) -> Result<G, CurveError> {
let mut encoding = G::Repr::default();
encoding.as_mut().copy_from_slice(slice);
let point = Option::<G>::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)?;
}
Ok(point)
}

View file

@ -2,12 +2,12 @@ use std::{marker::PhantomData, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use group::ff::{Field, PrimeField};
use group::{ff::{Field, PrimeField}, GroupEncoding};
use multiexp::{multiexp_vartime, BatchVerifier};
use crate::{
curve::Curve,
curve::{Curve, F_from_slice, G_from_slice},
FrostError, FrostParams, FrostKeys,
schnorr::{self, SchnorrSignature},
validate_map
@ -43,7 +43,7 @@ fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
// Step 3: Generate public commitments
commitments.push(C::GENERATOR_TABLE * coefficients[i]);
// Serialize them for publication
serialized.extend(&C::G_to_bytes(&commitments[i]));
serialized.extend(commitments[i].to_bytes().as_ref());
}
// Step 2: Provide a proof of knowledge
@ -59,7 +59,7 @@ fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
challenge::<C>(
context,
params.i(),
&C::G_to_bytes(&(C::GENERATOR_TABLE * r)),
(C::GENERATOR_TABLE * r).to_bytes().as_ref(),
&serialized
)
).serialize()
@ -90,11 +90,11 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
#[allow(non_snake_case)]
let R_bytes = |l| &serialized[&l][commitments_len .. commitments_len + C::G_len()];
#[allow(non_snake_case)]
let R = |l| C::G_from_slice(R_bytes(l)).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
let R = |l| G_from_slice::<C::G>(R_bytes(l)).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
#[allow(non_snake_case)]
let Am = |l| &serialized[&l][0 .. commitments_len];
let s = |l| C::F_from_slice(
let s = |l| F_from_slice::<C::F>(
&serialized[&l][commitments_len + C::G_len() ..]
).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
@ -103,7 +103,7 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
let mut these_commitments = vec![];
for c in 0 .. usize::from(params.t()) {
these_commitments.push(
C::G_from_slice(
G_from_slice::<C::G>(
&serialized[&l][(c * C::G_len()) .. ((c + 1) * C::G_len())]
).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))?
);
@ -166,7 +166,7 @@ fn generate_key_r2<R: RngCore + CryptoRng, C: Curve>(
continue;
}
res.insert(l, C::F_to_bytes(&polynomial(&coefficients, l)));
res.insert(l, polynomial(&coefficients, l).to_repr().as_ref().to_vec());
}
// Calculate our own share
@ -199,13 +199,13 @@ fn complete_r2<R: RngCore + CryptoRng, C: Curve>(
validate_map(
&mut serialized,
&(1 ..= params.n()).into_iter().collect::<Vec<_>>(),
(params.i(), C::F_to_bytes(&secret_share))
(params.i(), secret_share.to_repr().as_ref().to_vec())
)?;
// Step 2. Verify each share
let mut shares = HashMap::new();
for (l, share) in serialized {
shares.insert(l, C::F_from_slice(&share).map_err(|_| FrostError::InvalidShare(l))?);
shares.insert(l, F_from_slice::<C::F>(&share).map_err(|_| FrostError::InvalidShare(l))?);
}
// Calculate the exponent for a given participant and apply it to a series of commitments

View file

@ -3,12 +3,12 @@ use std::collections::HashMap;
use thiserror::Error;
use group::ff::{Field, PrimeField};
use group::{ff::{Field, PrimeField}, GroupEncoding};
mod schnorr;
pub mod curve;
use curve::Curve;
use curve::{Curve, F_from_slice, G_from_slice};
pub mod key_gen;
pub mod algorithm;
pub mod sign;
@ -213,10 +213,10 @@ impl<C: Curve> FrostKeys<C> {
serialized.extend(&self.params.t.to_be_bytes());
serialized.extend(&self.params.n.to_be_bytes());
serialized.extend(&self.params.i.to_be_bytes());
serialized.extend(&C::F_to_bytes(&self.secret_share));
serialized.extend(&C::G_to_bytes(&self.group_key));
serialized.extend(self.secret_share.to_repr().as_ref());
serialized.extend(self.group_key.to_bytes().as_ref());
for l in 1 ..= self.params.n.into() {
serialized.extend(&C::G_to_bytes(&self.verification_shares[&l]));
serialized.extend(self.verification_shares[&l].to_bytes().as_ref());
}
serialized
}
@ -253,10 +253,10 @@ impl<C: Curve> FrostKeys<C> {
let i = u16::from_be_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap());
cursor += 2;
let secret_share = C::F_from_slice(&serialized[cursor .. (cursor + C::F_len())])
let secret_share = F_from_slice::<C::F>(&serialized[cursor .. (cursor + C::F_len())])
.map_err(|_| FrostError::InternalError("invalid secret share".to_string()))?;
cursor += C::F_len();
let group_key = C::G_from_slice(&serialized[cursor .. (cursor + C::G_len())])
let group_key = G_from_slice::<C::G>(&serialized[cursor .. (cursor + C::G_len())])
.map_err(|_| FrostError::InternalError("invalid group key".to_string()))?;
cursor += C::G_len();
@ -264,7 +264,7 @@ impl<C: Curve> FrostKeys<C> {
for l in 1 ..= n {
verification_shares.insert(
l,
C::G_from_slice(&serialized[cursor .. (cursor + C::G_len())])
G_from_slice::<C::G>(&serialized[cursor .. (cursor + C::G_len())])
.map_err(|_| FrostError::InternalError("invalid verification share".to_string()))?
);
cursor += C::G_len();

View file

@ -1,6 +1,6 @@
use rand_core::{RngCore, CryptoRng};
use group::ff::Field;
use group::{ff::{Field, PrimeField}, GroupEncoding};
use multiexp::BatchVerifier;
@ -16,8 +16,8 @@ pub struct SchnorrSignature<C: Curve> {
impl<C: Curve> SchnorrSignature<C> {
pub fn serialize(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(C::G_len() + C::F_len());
res.extend(C::G_to_bytes(&self.R));
res.extend(C::F_to_bytes(&self.s));
res.extend(self.R.to_bytes().as_ref());
res.extend(self.s.to_repr().as_ref());
res
}
}

View file

@ -3,12 +3,12 @@ use std::{sync::Arc, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use group::ff::Field;
use group::{ff::{Field, PrimeField}, GroupEncoding};
use transcript::Transcript;
use crate::{
curve::Curve,
curve::{Curve, F_from_slice, G_from_slice},
FrostError,
FrostParams, FrostKeys, FrostView,
algorithm::Algorithm,
@ -85,8 +85,8 @@ fn preprocess<R: RngCore + CryptoRng, C: Curve, A: Algorithm<C>>(
C::random_nonce(params.view().secret_share(), &mut *rng)
];
let commitments = [C::GENERATOR_TABLE * nonces[0], C::GENERATOR_TABLE * nonces[1]];
let mut serialized = C::G_to_bytes(&commitments[0]);
serialized.extend(&C::G_to_bytes(&commitments[1]));
let mut serialized = commitments[0].to_bytes().as_ref().to_vec();
serialized.extend(commitments[1].to_bytes().as_ref());
serialized.extend(
&params.algorithm.preprocess_addendum(
@ -129,7 +129,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
transcript.domain_separate(b"FROST");
// Include the offset, if one exists
if let Some(offset) = params.keys.offset {
transcript.append_message(b"offset", &C::F_to_bytes(&offset));
transcript.append_message(b"offset", offset.to_repr().as_ref());
}
}
@ -148,7 +148,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let mut read_commitment = |c, label| {
let commitment = &commitments[c .. (c + C::G_len())];
transcript.append_message(label, commitment);
C::G_from_slice(commitment).map_err(|_| FrostError::InvalidCommitment(*l))
G_from_slice::<C::G>(commitment).map_err(|_| FrostError::InvalidCommitment(*l))
};
#[allow(non_snake_case)]
@ -176,15 +176,13 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let R = {
B.values().map(|B| B[0]).sum::<C::G>() + (B.values().map(|B| B[1]).sum::<C::G>() * binding)
};
let share = C::F_to_bytes(
&params.algorithm.sign_share(
&params.view,
R,
binding,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
msg
)
);
let share = params.algorithm.sign_share(
&params.view,
R,
binding,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
msg
).to_repr().as_ref().to_vec();
Ok((Package { B, binding, R, share: share.clone() }, share))
}
@ -203,7 +201,7 @@ fn complete<C: Curve, A: Algorithm<C>>(
let mut responses = HashMap::new();
let mut sum = C::F::zero();
for l in &sign_params.view.included {
let part = C::F_from_slice(&shares[l]).map_err(|_| FrostError::InvalidShare(*l))?;
let part = F_from_slice::<C::F>(&shares[l]).map_err(|_| FrostError::InvalidShare(*l))?;
sum += part;
responses.insert(*l, part);
}

View file

@ -2,7 +2,7 @@ use std::{marker::PhantomData, sync::Arc, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use group::ff::Field;
use group::{ff::Field, GroupEncoding};
use crate::{
Curve, FrostKeys, schnorr::{self, SchnorrSignature}, algorithm::{Hram, Schnorr},
@ -96,7 +96,7 @@ pub struct TestHram<C: Curve> {
impl<C: Curve> Hram<C> for TestHram<C> {
#[allow(non_snake_case)]
fn hram(R: &C::G, A: &C::G, m: &[u8]) -> C::F {
C::hash_to_F(b"challenge", &[&C::G_to_bytes(R), &C::G_to_bytes(A), m].concat())
C::hash_to_F(b"challenge", &[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat())
}
}

View file

@ -2,8 +2,10 @@ use std::{sync::Arc, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use group::{ff::PrimeField, GroupEncoding};
use crate::{
Curve, FrostKeys,
curve::{Curve, F_from_slice, G_from_slice}, FrostKeys,
algorithm::{Schnorr, Hram},
sign::{PreprocessPackage, SignMachine, SignatureMachine, AlgorithmMachine},
tests::{curve::test_curve, schnorr::test_schnorr, recover}
@ -25,7 +27,7 @@ pub struct Vectors {
// Load these vectors into FrostKeys using a custom serialization it'll deserialize
fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, FrostKeys<C>> {
let shares = vectors.shares.iter().map(
|secret| C::F_from_slice(&hex::decode(secret).unwrap()).unwrap()
|secret| F_from_slice::<C::F>(&hex::decode(secret).unwrap()).unwrap()
).collect::<Vec<_>>();
let verification_shares = shares.iter().map(
|secret| C::GENERATOR * secret
@ -39,10 +41,10 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, FrostKe
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(shares[usize::from(i) - 1].to_repr().as_ref());
serialized.extend(&hex::decode(vectors.group_key).unwrap());
for share in &verification_shares {
serialized.extend(&C::G_to_bytes(share));
serialized.extend(share.to_bytes().as_ref());
}
let these_keys = FrostKeys::<C>::deserialize(&serialized).unwrap();
@ -50,7 +52,7 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, FrostKe
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);
assert_eq!(&hex::encode(these_keys.group_key().to_bytes().as_ref()), vectors.group_key);
keys.insert(i, these_keys);
}
@ -68,14 +70,14 @@ pub fn test_with_vectors<
// Test against the vectors
let keys = vectors_to_multisig_keys::<C>(&vectors);
let group_key = C::G_from_slice(&hex::decode(vectors.group_key).unwrap()).unwrap();
let group_key = G_from_slice::<C::G>(&hex::decode(vectors.group_key).unwrap()).unwrap();
assert_eq!(
C::GENERATOR * C::F_from_slice(&hex::decode(vectors.group_secret).unwrap()).unwrap(),
C::GENERATOR * F_from_slice::<C::F>(&hex::decode(vectors.group_secret).unwrap()).unwrap(),
group_key
);
assert_eq!(
recover(&keys),
C::F_from_slice(&hex::decode(vectors.group_secret).unwrap()).unwrap()
F_from_slice::<C::F>(&hex::decode(vectors.group_secret).unwrap()).unwrap()
);
let mut machines = vec![];
@ -94,13 +96,13 @@ pub fn test_with_vectors<
let mut c = 0;
let mut machines = machines.drain(..).map(|(i, machine)| {
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()
F_from_slice::<C::F>(&hex::decode(vectors.nonces[c][0]).unwrap()).unwrap(),
F_from_slice::<C::F>(&hex::decode(vectors.nonces[c][1]).unwrap()).unwrap()
];
c += 1;
let mut serialized = C::G_to_bytes(&(C::GENERATOR * nonces[0]));
serialized.extend(&C::G_to_bytes(&(C::GENERATOR * nonces[1])));
let mut serialized = (C::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, serialized: serialized.clone() }
@ -127,8 +129,8 @@ pub fn test_with_vectors<
for (_, machine) in machines.drain() {
let sig = machine.complete(shares.clone()).unwrap();
let mut serialized = C::G_to_bytes(&sig.R);
serialized.extend(C::F_to_bytes(&sig.s));
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);
}
}

View file

@ -18,6 +18,8 @@ serde_json = "1.0"
curve25519-dalek = { version = "3", features = ["std"] }
blake2 = "0.10"
group = "0.12"
transcript = { package = "flexible-transcript", path = "../crypto/transcript", features = ["recommended"] }
dalek-ff-group = { path = "../crypto/dalek-ff-group" }
frost = { package = "modular-frost", path = "../crypto/frost" }

View file

@ -2,8 +2,9 @@ use std::{sync::Arc, collections::HashMap};
use rand_core::OsRng;
use transcript::{Transcript, RecommendedTranscript};
use group::GroupEncoding;
use transcript::{Transcript, RecommendedTranscript};
use frost::{curve::Curve, FrostKeys, sign::{PreprocessMachine, SignMachine, SignatureMachine}};
use crate::{coin::{CoinError, Output, Coin}, SignError, Network};
@ -31,7 +32,7 @@ impl<C: Curve> WalletKeys<C> {
let mut transcript = RecommendedTranscript::new(DST);
transcript.append_message(b"chain", chain);
transcript.append_message(b"curve", C::ID);
transcript.append_message(b"group_key", &C::G_to_bytes(&self.keys.group_key()));
transcript.append_message(b"group_key", self.keys.group_key().to_bytes().as_ref());
self.keys.offset(C::hash_to_F(DST, &transcript.challenge(b"offset")))
}
}