mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-24 08:08:51 +00:00
Add FROST key promotion
Closes https://github.com/serai-dex/serai/issues/72. Adds a trait, with a commented impl for a semi-unsafe niche feature, which will be used in https://github.com/serai-dex/serai/issues/73.
This commit is contained in:
parent
885d816309
commit
454b73aec3
8 changed files with 283 additions and 7 deletions
|
@ -60,7 +60,7 @@ pub enum DLEqError {
|
||||||
InvalidProof,
|
InvalidProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub struct DLEqProof<G: PrimeGroup> {
|
pub struct DLEqProof<G: PrimeGroup> {
|
||||||
c: G::Scalar,
|
c: G::Scalar,
|
||||||
s: G::Scalar,
|
s: G::Scalar,
|
||||||
|
|
|
@ -27,7 +27,7 @@ p256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], opti
|
||||||
k256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true }
|
k256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true }
|
||||||
dalek-ff-group = { path = "../dalek-ff-group", version = "0.1", optional = true }
|
dalek-ff-group = { path = "../dalek-ff-group", version = "0.1", optional = true }
|
||||||
|
|
||||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" }
|
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"], version = "0.1" }
|
||||||
|
|
||||||
multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] }
|
multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] }
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,10 @@ mod schnorr;
|
||||||
|
|
||||||
pub mod curve;
|
pub mod curve;
|
||||||
use curve::Curve;
|
use curve::Curve;
|
||||||
|
|
||||||
pub mod key_gen;
|
pub mod key_gen;
|
||||||
|
pub mod promote;
|
||||||
|
|
||||||
pub mod algorithm;
|
pub mod algorithm;
|
||||||
pub mod sign;
|
pub mod sign;
|
||||||
|
|
||||||
|
|
142
crypto/frost/src/promote.rs
Normal file
142
crypto/frost/src/promote.rs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
use std::{
|
||||||
|
marker::PhantomData,
|
||||||
|
io::{self, Read, Write},
|
||||||
|
sync::Arc,
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
use group::GroupEncoding;
|
||||||
|
|
||||||
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
|
use dleq::DLEqProof;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
curve::{CurveError, Curve},
|
||||||
|
FrostError, FrostCore, FrostKeys, validate_map,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Promote a set of keys to another Curve definition
|
||||||
|
pub trait CurvePromote<C2: Curve> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn _bound_C2(_c2: C2) {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn promote(self) -> FrostKeys<C2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement promotion to different ciphersuites, panicking if the generators are different
|
||||||
|
// Commented due to lack of practical benefit. While it'd have interoperability benefits, those
|
||||||
|
// would have their own DKG process which isn't compatible anyways. This becomes unsafe code
|
||||||
|
// that'll never be used but we're bound to support
|
||||||
|
/*
|
||||||
|
impl<C1: Curve, C2: Curve> CurvePromote<C2> for FrostKeys<C1>
|
||||||
|
where
|
||||||
|
C2: Curve<F = C1::F, G = C1::G>,
|
||||||
|
{
|
||||||
|
fn promote(self) -> FrostKeys<C2> {
|
||||||
|
assert_eq!(C::GENERATOR, C2::GENERATOR);
|
||||||
|
|
||||||
|
FrostKeys {
|
||||||
|
core: Arc::new(FrostCore {
|
||||||
|
params: self.core.params,
|
||||||
|
secret_share: self.core.secret_share,
|
||||||
|
group_key: self.core.group_key,
|
||||||
|
verification_shares: self.core.verification_shares(),
|
||||||
|
}),
|
||||||
|
offset: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn transcript<G: GroupEncoding>(key: G, i: u16) -> RecommendedTranscript {
|
||||||
|
let mut transcript = RecommendedTranscript::new(b"FROST Generator Update");
|
||||||
|
transcript.append_message(b"group_key", key.to_bytes().as_ref());
|
||||||
|
transcript.append_message(b"participant", &i.to_be_bytes());
|
||||||
|
transcript
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct GeneratorProof<C: Curve> {
|
||||||
|
share: C::G,
|
||||||
|
proof: DLEqProof<C::G>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Curve> GeneratorProof<C> {
|
||||||
|
pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
writer.write_all(self.share.to_bytes().as_ref())?;
|
||||||
|
self.proof.serialize(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<R: Read>(reader: &mut R) -> Result<GeneratorProof<C>, CurveError> {
|
||||||
|
Ok(GeneratorProof {
|
||||||
|
share: C::read_G(reader)?,
|
||||||
|
proof: DLEqProof::deserialize(reader).map_err(|_| CurveError::InvalidScalar)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GeneratorPromotion<C1: Curve, C2: Curve> {
|
||||||
|
base: FrostKeys<C1>,
|
||||||
|
proof: GeneratorProof<C1>,
|
||||||
|
_c2: PhantomData<C2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Promote a set of keys from one generator to another
|
||||||
|
// The linear DLEq proofs are much more efficient than an exponential key gen
|
||||||
|
impl<C1: Curve, C2: Curve> GeneratorPromotion<C1, C2>
|
||||||
|
where
|
||||||
|
C2: Curve<F = C1::F, G = C1::G>,
|
||||||
|
{
|
||||||
|
pub fn promote<R: RngCore + CryptoRng>(
|
||||||
|
rng: &mut R,
|
||||||
|
base: FrostKeys<C1>,
|
||||||
|
) -> (GeneratorPromotion<C1, C2>, GeneratorProof<C1>) {
|
||||||
|
// Do a DLEqProof for the new generator
|
||||||
|
let proof = GeneratorProof {
|
||||||
|
share: C2::generator() * base.secret_share(),
|
||||||
|
proof: DLEqProof::prove(
|
||||||
|
rng,
|
||||||
|
&mut transcript(base.core.group_key(), base.params().i),
|
||||||
|
&[C1::generator(), C2::generator()],
|
||||||
|
base.secret_share(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
(GeneratorPromotion { base, proof, _c2: PhantomData::<C2> }, proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete(
|
||||||
|
self,
|
||||||
|
proofs: &HashMap<u16, GeneratorProof<C1>>,
|
||||||
|
) -> Result<FrostKeys<C2>, FrostError> {
|
||||||
|
let params = self.base.params();
|
||||||
|
validate_map(proofs, &(1 ..= params.n).collect::<Vec<_>>(), params.i)?;
|
||||||
|
|
||||||
|
let original_shares = self.base.verification_shares();
|
||||||
|
|
||||||
|
let mut verification_shares = HashMap::new();
|
||||||
|
verification_shares.insert(params.i, self.proof.share);
|
||||||
|
for (i, proof) in proofs {
|
||||||
|
let i = *i;
|
||||||
|
proof
|
||||||
|
.proof
|
||||||
|
.verify(
|
||||||
|
&mut transcript(self.base.core.group_key(), i),
|
||||||
|
&[C1::generator(), C2::generator()],
|
||||||
|
&[original_shares[&i], proof.share],
|
||||||
|
)
|
||||||
|
.map_err(|_| FrostError::InvalidProofOfKnowledge(i))?;
|
||||||
|
verification_shares.insert(i, proof.share);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FrostKeys {
|
||||||
|
core: Arc::new(FrostCore::new(params, self.base.secret_share(), verification_shares)),
|
||||||
|
offset: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
// Test suites for public usage
|
// Test suites for public usage
|
||||||
pub mod curve;
|
pub mod curve;
|
||||||
pub mod schnorr;
|
pub mod schnorr;
|
||||||
|
pub mod promote;
|
||||||
pub mod vectors;
|
pub mod vectors;
|
||||||
|
|
||||||
// Literal test definitions to run during `cargo test`
|
// Literal test definitions to run during `cargo test`
|
||||||
|
|
125
crypto/frost/src/tests/promote.rs
Normal file
125
crypto/frost/src/tests/promote.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
use std::{marker::PhantomData, collections::HashMap};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use group::Group;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Curve, // FrostKeys,
|
||||||
|
promote::{GeneratorPromotion /* CurvePromote */},
|
||||||
|
tests::{clone_without, key_gen, schnorr::sign_core},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
|
struct AltFunctions<C: Curve> {
|
||||||
|
_curve: PhantomData<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Curve> Curve for AltFunctions<C> {
|
||||||
|
type F = C::F;
|
||||||
|
type G = C::G;
|
||||||
|
|
||||||
|
const ID: &'static [u8] = b"alt_functions";
|
||||||
|
|
||||||
|
fn generator() -> Self::G {
|
||||||
|
C::generator()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||||
|
C::hash_msg(&[msg, b"alt"].concat())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_binding_factor(binding: &[u8]) -> Self::F {
|
||||||
|
C::hash_to_F(b"rho_alt", binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||||
|
C::hash_to_F(&[dst, b"alt"].concat(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test promotion of FROST keys to another set of functions for interoperability
|
||||||
|
fn test_ciphersuite_promotion<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
|
let keys = key_gen::<_, C>(&mut *rng);
|
||||||
|
for keys in keys.values() {
|
||||||
|
let promoted: FrostKeys<AltFunctions<C>> = keys.clone().promote();
|
||||||
|
// Verify equivalence via their serializations, minus the ID's length and ID itself
|
||||||
|
assert_eq!(
|
||||||
|
keys.serialize()[(4 + C::ID.len()) ..],
|
||||||
|
promoted.serialize()[(4 + AltFunctions::<C>::ID.len()) ..]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
|
struct AltGenerator<C: Curve> {
|
||||||
|
_curve: PhantomData<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Curve> Curve for AltGenerator<C> {
|
||||||
|
type F = C::F;
|
||||||
|
type G = C::G;
|
||||||
|
|
||||||
|
const ID: &'static [u8] = b"alt_generator";
|
||||||
|
|
||||||
|
fn generator() -> Self::G {
|
||||||
|
C::G::generator() * C::hash_to_F(b"FROST_tests", b"generator")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||||
|
C::hash_msg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_binding_factor(binding: &[u8]) -> Self::F {
|
||||||
|
C::hash_binding_factor(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||||
|
C::hash_to_F(dst, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test promotion of FROST keys to another generator
|
||||||
|
fn test_generator_promotion<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
|
// A seeded RNG can theoretically generate for C1 and C2, verifying promotion that way?
|
||||||
|
// TODO
|
||||||
|
let keys = key_gen::<_, C>(&mut *rng);
|
||||||
|
|
||||||
|
let mut promotions = HashMap::new();
|
||||||
|
let mut proofs = HashMap::new();
|
||||||
|
for (i, keys) in &keys {
|
||||||
|
let promotion = GeneratorPromotion::<_, AltGenerator<C>>::promote(&mut *rng, keys.clone());
|
||||||
|
promotions.insert(*i, promotion.0);
|
||||||
|
proofs.insert(*i, promotion.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_keys = HashMap::new();
|
||||||
|
let mut group_key = None;
|
||||||
|
let mut verification_shares = None;
|
||||||
|
for (i, promoting) in promotions.drain() {
|
||||||
|
let promoted = promoting.complete(&clone_without(&proofs, &i)).unwrap();
|
||||||
|
assert_eq!(keys[&i].params(), promoted.params());
|
||||||
|
assert_eq!(keys[&i].secret_share(), promoted.secret_share());
|
||||||
|
|
||||||
|
if group_key.is_none() {
|
||||||
|
group_key = Some(keys[&i].group_key());
|
||||||
|
verification_shares = Some(keys[&i].verification_shares());
|
||||||
|
}
|
||||||
|
assert_eq!(keys[&i].group_key(), group_key.unwrap());
|
||||||
|
assert_eq!(&keys[&i].verification_shares(), verification_shares.as_ref().unwrap());
|
||||||
|
|
||||||
|
new_keys.insert(i, promoted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign with the keys to ensure their integrity
|
||||||
|
sign_core(rng, &new_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_promotion<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
|
// test_ciphersuite_promotion::<_, C>(rng);
|
||||||
|
test_generator_promotion::<_, C>(rng);
|
||||||
|
}
|
|
@ -75,15 +75,16 @@ pub(crate) fn core_batch_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_core<R: RngCore + CryptoRng, C: Curve>(
|
pub(crate) fn sign_core<R: RngCore + CryptoRng, C: Curve>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
group_key: C::G,
|
|
||||||
keys: &HashMap<u16, FrostKeys<C>>,
|
keys: &HashMap<u16, FrostKeys<C>>,
|
||||||
) {
|
) {
|
||||||
const MESSAGE: &[u8] = b"Hello, World!";
|
const MESSAGE: &[u8] = b"Hello, World!";
|
||||||
|
|
||||||
let machines = algorithm_machines(rng, Schnorr::<C, TestHram<C>>::new(), keys);
|
let machines = algorithm_machines(rng, Schnorr::<C, TestHram<C>>::new(), keys);
|
||||||
let sig = sign_test(&mut *rng, machines, MESSAGE);
|
let sig = sign_test(&mut *rng, machines, MESSAGE);
|
||||||
|
|
||||||
|
let group_key = keys[&1].group_key();
|
||||||
assert!(schnorr::verify(group_key, TestHram::<C>::hram(&sig.R, &group_key, MESSAGE), &sig));
|
assert!(schnorr::verify(group_key, TestHram::<C>::hram(&sig.R, &group_key, MESSAGE), &sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ impl<C: Curve> Hram<C> for TestHram<C> {
|
||||||
|
|
||||||
fn sign<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
fn sign<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
let keys = key_gen::<_, C>(&mut *rng);
|
let keys = key_gen::<_, C>(&mut *rng);
|
||||||
sign_core(rng, keys[&1].group_key(), &keys);
|
sign_core(rng, &keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_with_offset<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
fn sign_with_offset<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
|
@ -112,8 +113,9 @@ fn sign_with_offset<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
keys.insert(i, keys[&i].offset(offset));
|
keys.insert(i, keys[&i].offset(offset));
|
||||||
}
|
}
|
||||||
let offset_key = group_key + (C::generator() * offset);
|
let offset_key = group_key + (C::generator() * offset);
|
||||||
|
assert_eq!(keys[&1].group_key(), offset_key);
|
||||||
|
|
||||||
sign_core(rng, offset_key, &keys);
|
sign_core(rng, &keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_schnorr<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
pub fn test_schnorr<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
|
|
|
@ -9,7 +9,9 @@ use crate::{
|
||||||
FrostCore, FrostKeys,
|
FrostCore, FrostKeys,
|
||||||
algorithm::{Schnorr, Hram},
|
algorithm::{Schnorr, Hram},
|
||||||
sign::{PreprocessPackage, SignMachine, SignatureMachine, AlgorithmMachine},
|
sign::{PreprocessPackage, SignMachine, SignatureMachine, AlgorithmMachine},
|
||||||
tests::{clone_without, curve::test_curve, schnorr::test_schnorr, recover},
|
tests::{
|
||||||
|
clone_without, curve::test_curve, schnorr::test_schnorr, promote::test_promotion, recover,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Vectors {
|
pub struct Vectors {
|
||||||
|
@ -66,6 +68,7 @@ pub fn test_with_vectors<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(
|
||||||
// Do basic tests before trying the vectors
|
// Do basic tests before trying the vectors
|
||||||
test_curve::<_, C>(&mut *rng);
|
test_curve::<_, C>(&mut *rng);
|
||||||
test_schnorr::<_, C>(rng);
|
test_schnorr::<_, C>(rng);
|
||||||
|
test_promotion::<_, C>(rng);
|
||||||
|
|
||||||
// Test against the vectors
|
// Test against the vectors
|
||||||
let keys = vectors_to_multisig_keys::<C>(&vectors);
|
let keys = vectors_to_multisig_keys::<C>(&vectors);
|
||||||
|
|
Loading…
Reference in a new issue