mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
git checkout -f next ./crypto
Proceeds to remove the eVRF DKG after, only keeping what's relevant to this branch alone.
This commit is contained in:
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.81"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror = { version = "1", default-features = false, optional = true }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
|
||||
@@ -42,7 +42,7 @@ ciphersuite = { path = "../ciphersuite", default-features = false, features = ["
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"thiserror",
|
||||
"thiserror/std",
|
||||
|
||||
"rand_core/std",
|
||||
|
||||
|
||||
@@ -98,11 +98,11 @@ fn ecdh<C: Ciphersuite>(private: &Zeroizing<C::F>, public: C::G) -> Zeroizing<C:
|
||||
|
||||
// Each ecdh must be distinct. Reuse of an ecdh for multiple ciphers will cause the messages to be
|
||||
// leaked.
|
||||
fn cipher<C: Ciphersuite>(context: &str, ecdh: &Zeroizing<C::G>) -> ChaCha20 {
|
||||
fn cipher<C: Ciphersuite>(context: [u8; 32], ecdh: &Zeroizing<C::G>) -> ChaCha20 {
|
||||
// Ideally, we'd box this transcript with ZAlloc, yet that's only possible on nightly
|
||||
// TODO: https://github.com/serai-dex/serai/issues/151
|
||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption v0.2");
|
||||
transcript.append_message(b"context", context.as_bytes());
|
||||
transcript.append_message(b"context", context);
|
||||
|
||||
transcript.domain_separate(b"encryption_key");
|
||||
|
||||
@@ -134,7 +134,7 @@ fn cipher<C: Ciphersuite>(context: &str, ecdh: &Zeroizing<C::G>) -> ChaCha20 {
|
||||
|
||||
fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
|
||||
rng: &mut R,
|
||||
context: &str,
|
||||
context: [u8; 32],
|
||||
from: Participant,
|
||||
to: C::G,
|
||||
mut msg: Zeroizing<E>,
|
||||
@@ -197,7 +197,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
context: &str,
|
||||
context: [u8; 32],
|
||||
from: Participant,
|
||||
) {
|
||||
// Invalidate the message by specifying a new key/Schnorr PoP
|
||||
@@ -219,7 +219,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||
pub(crate) fn invalidate_share_serialization<R: RngCore + CryptoRng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
context: &str,
|
||||
context: [u8; 32],
|
||||
from: Participant,
|
||||
to: C::G,
|
||||
) {
|
||||
@@ -243,7 +243,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||
pub(crate) fn invalidate_share_value<R: RngCore + CryptoRng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
context: &str,
|
||||
context: [u8; 32],
|
||||
from: Participant,
|
||||
to: C::G,
|
||||
) {
|
||||
@@ -300,14 +300,14 @@ impl<C: Ciphersuite> EncryptionKeyProof<C> {
|
||||
// This still doesn't mean the DKG offers an authenticated channel. The per-message keys have no
|
||||
// root of trust other than their existence in the assumed-to-exist external authenticated channel.
|
||||
fn pop_challenge<C: Ciphersuite>(
|
||||
context: &str,
|
||||
context: [u8; 32],
|
||||
nonce: C::G,
|
||||
key: C::G,
|
||||
sender: Participant,
|
||||
msg: &[u8],
|
||||
) -> C::F {
|
||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Proof of Possession v0.2");
|
||||
transcript.append_message(b"context", context.as_bytes());
|
||||
transcript.append_message(b"context", context);
|
||||
|
||||
transcript.domain_separate(b"proof_of_possession");
|
||||
|
||||
@@ -323,9 +323,9 @@ fn pop_challenge<C: Ciphersuite>(
|
||||
C::hash_to_F(b"DKG-encryption-proof_of_possession", &transcript.challenge(b"schnorr"))
|
||||
}
|
||||
|
||||
fn encryption_key_transcript(context: &str) -> RecommendedTranscript {
|
||||
fn encryption_key_transcript(context: [u8; 32]) -> RecommendedTranscript {
|
||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Correctness Proof v0.2");
|
||||
transcript.append_message(b"context", context.as_bytes());
|
||||
transcript.append_message(b"context", context);
|
||||
transcript
|
||||
}
|
||||
|
||||
@@ -337,58 +337,17 @@ pub(crate) enum DecryptionError {
|
||||
InvalidProof,
|
||||
}
|
||||
|
||||
// A simple box for managing encryption.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Encryption<C: Ciphersuite> {
|
||||
context: String,
|
||||
i: Option<Participant>,
|
||||
enc_key: Zeroizing<C::F>,
|
||||
enc_pub_key: C::G,
|
||||
// A simple box for managing decryption.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Decryption<C: Ciphersuite> {
|
||||
context: [u8; 32],
|
||||
enc_keys: HashMap<Participant, C::G>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> fmt::Debug for Encryption<C> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt
|
||||
.debug_struct("Encryption")
|
||||
.field("context", &self.context)
|
||||
.field("i", &self.i)
|
||||
.field("enc_pub_key", &self.enc_pub_key)
|
||||
.field("enc_keys", &self.enc_keys)
|
||||
.finish_non_exhaustive()
|
||||
impl<C: Ciphersuite> Decryption<C> {
|
||||
pub(crate) fn new(context: [u8; 32]) -> Self {
|
||||
Self { context, enc_keys: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Zeroize for Encryption<C> {
|
||||
fn zeroize(&mut self) {
|
||||
self.enc_key.zeroize();
|
||||
self.enc_pub_key.zeroize();
|
||||
for (_, mut value) in self.enc_keys.drain() {
|
||||
value.zeroize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Encryption<C> {
|
||||
pub(crate) fn new<R: RngCore + CryptoRng>(
|
||||
context: String,
|
||||
i: Option<Participant>,
|
||||
rng: &mut R,
|
||||
) -> Self {
|
||||
let enc_key = Zeroizing::new(C::random_nonzero_F(rng));
|
||||
Self {
|
||||
context,
|
||||
i,
|
||||
enc_pub_key: C::generator() * enc_key.deref(),
|
||||
enc_key,
|
||||
enc_keys: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn registration<M: Message>(&self, msg: M) -> EncryptionKeyMessage<C, M> {
|
||||
EncryptionKeyMessage { msg, enc_key: self.enc_pub_key }
|
||||
}
|
||||
|
||||
pub(crate) fn register<M: Message>(
|
||||
&mut self,
|
||||
participant: Participant,
|
||||
@@ -402,13 +361,109 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||
msg.msg
|
||||
}
|
||||
|
||||
// Given a message, and the intended decryptor, and a proof for its key, decrypt the message.
|
||||
// Returns None if the key was wrong.
|
||||
pub(crate) fn decrypt_with_proof<E: Encryptable>(
|
||||
&self,
|
||||
from: Participant,
|
||||
decryptor: Participant,
|
||||
mut msg: EncryptedMessage<C, E>,
|
||||
// There's no encryption key proof if the accusation is of an invalid signature
|
||||
proof: Option<EncryptionKeyProof<C>>,
|
||||
) -> Result<Zeroizing<E>, DecryptionError> {
|
||||
if !msg.pop.verify(
|
||||
msg.key,
|
||||
pop_challenge::<C>(self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
||||
) {
|
||||
Err(DecryptionError::InvalidSignature)?;
|
||||
}
|
||||
|
||||
if let Some(proof) = proof {
|
||||
// Verify this is the decryption key for this message
|
||||
proof
|
||||
.dleq
|
||||
.verify(
|
||||
&mut encryption_key_transcript(self.context),
|
||||
&[C::generator(), msg.key],
|
||||
&[self.enc_keys[&decryptor], *proof.key],
|
||||
)
|
||||
.map_err(|_| DecryptionError::InvalidProof)?;
|
||||
|
||||
cipher::<C>(self.context, &proof.key).apply_keystream(msg.msg.as_mut().as_mut());
|
||||
Ok(msg.msg)
|
||||
} else {
|
||||
Err(DecryptionError::InvalidProof)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A simple box for managing encryption.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Encryption<C: Ciphersuite> {
|
||||
context: [u8; 32],
|
||||
i: Participant,
|
||||
enc_key: Zeroizing<C::F>,
|
||||
enc_pub_key: C::G,
|
||||
decryption: Decryption<C>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> fmt::Debug for Encryption<C> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt
|
||||
.debug_struct("Encryption")
|
||||
.field("context", &self.context)
|
||||
.field("i", &self.i)
|
||||
.field("enc_pub_key", &self.enc_pub_key)
|
||||
.field("decryption", &self.decryption)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Zeroize for Encryption<C> {
|
||||
fn zeroize(&mut self) {
|
||||
self.enc_key.zeroize();
|
||||
self.enc_pub_key.zeroize();
|
||||
for (_, mut value) in self.decryption.enc_keys.drain() {
|
||||
value.zeroize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Encryption<C> {
|
||||
pub(crate) fn new<R: RngCore + CryptoRng>(
|
||||
context: [u8; 32],
|
||||
i: Participant,
|
||||
rng: &mut R,
|
||||
) -> Self {
|
||||
let enc_key = Zeroizing::new(C::random_nonzero_F(rng));
|
||||
Self {
|
||||
context,
|
||||
i,
|
||||
enc_pub_key: C::generator() * enc_key.deref(),
|
||||
enc_key,
|
||||
decryption: Decryption::new(context),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn registration<M: Message>(&self, msg: M) -> EncryptionKeyMessage<C, M> {
|
||||
EncryptionKeyMessage { msg, enc_key: self.enc_pub_key }
|
||||
}
|
||||
|
||||
pub(crate) fn register<M: Message>(
|
||||
&mut self,
|
||||
participant: Participant,
|
||||
msg: EncryptionKeyMessage<C, M>,
|
||||
) -> M {
|
||||
self.decryption.register(participant, msg)
|
||||
}
|
||||
|
||||
pub(crate) fn encrypt<R: RngCore + CryptoRng, E: Encryptable>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
participant: Participant,
|
||||
msg: Zeroizing<E>,
|
||||
) -> EncryptedMessage<C, E> {
|
||||
encrypt(rng, &self.context, self.i.unwrap(), self.enc_keys[&participant], msg)
|
||||
encrypt(rng, self.context, self.i, self.decryption.enc_keys[&participant], msg)
|
||||
}
|
||||
|
||||
pub(crate) fn decrypt<R: RngCore + CryptoRng, I: Copy + Zeroize, E: Encryptable>(
|
||||
@@ -426,18 +481,18 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||
batch,
|
||||
batch_id,
|
||||
msg.key,
|
||||
pop_challenge::<C>(&self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
||||
pop_challenge::<C>(self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
||||
);
|
||||
|
||||
let key = ecdh::<C>(&self.enc_key, msg.key);
|
||||
cipher::<C>(&self.context, &key).apply_keystream(msg.msg.as_mut().as_mut());
|
||||
cipher::<C>(self.context, &key).apply_keystream(msg.msg.as_mut().as_mut());
|
||||
(
|
||||
msg.msg,
|
||||
EncryptionKeyProof {
|
||||
key,
|
||||
dleq: DLEqProof::prove(
|
||||
rng,
|
||||
&mut encryption_key_transcript(&self.context),
|
||||
&mut encryption_key_transcript(self.context),
|
||||
&[C::generator(), msg.key],
|
||||
&self.enc_key,
|
||||
),
|
||||
@@ -445,38 +500,7 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||
)
|
||||
}
|
||||
|
||||
// Given a message, and the intended decryptor, and a proof for its key, decrypt the message.
|
||||
// Returns None if the key was wrong.
|
||||
pub(crate) fn decrypt_with_proof<E: Encryptable>(
|
||||
&self,
|
||||
from: Participant,
|
||||
decryptor: Participant,
|
||||
mut msg: EncryptedMessage<C, E>,
|
||||
// There's no encryption key proof if the accusation is of an invalid signature
|
||||
proof: Option<EncryptionKeyProof<C>>,
|
||||
) -> Result<Zeroizing<E>, DecryptionError> {
|
||||
if !msg.pop.verify(
|
||||
msg.key,
|
||||
pop_challenge::<C>(&self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
||||
) {
|
||||
Err(DecryptionError::InvalidSignature)?;
|
||||
}
|
||||
|
||||
if let Some(proof) = proof {
|
||||
// Verify this is the decryption key for this message
|
||||
proof
|
||||
.dleq
|
||||
.verify(
|
||||
&mut encryption_key_transcript(&self.context),
|
||||
&[C::generator(), msg.key],
|
||||
&[self.enc_keys[&decryptor], *proof.key],
|
||||
)
|
||||
.map_err(|_| DecryptionError::InvalidProof)?;
|
||||
|
||||
cipher::<C>(&self.context, &proof.key).apply_keystream(msg.msg.as_mut().as_mut());
|
||||
Ok(msg.msg)
|
||||
} else {
|
||||
Err(DecryptionError::InvalidProof)
|
||||
}
|
||||
pub(crate) fn into_decryption(self) -> Decryption<C> {
|
||||
self.decryption
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
use core::fmt::{self, Debug};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use thiserror::Error;
|
||||
|
||||
use zeroize::Zeroize;
|
||||
@@ -63,8 +62,7 @@ impl fmt::Display for Participant {
|
||||
}
|
||||
|
||||
/// Various errors possible during key generation.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "std", derive(Error))]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
||||
pub enum DkgError<B: Clone + PartialEq + Eq + Debug> {
|
||||
/// A parameter was zero.
|
||||
#[cfg_attr(feature = "std", error("a parameter was 0 (threshold {0}, participants {1})"))]
|
||||
@@ -205,25 +203,37 @@ mod lib {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the lagrange coefficient for a signing set.
|
||||
pub fn lagrange<F: PrimeField>(i: Participant, included: &[Participant]) -> F {
|
||||
let i_f = F::from(u64::from(u16::from(i)));
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub(crate) enum Interpolation<F: Zeroize + PrimeField> {
|
||||
Constant(Vec<F>),
|
||||
Lagrange,
|
||||
}
|
||||
|
||||
let mut num = F::ONE;
|
||||
let mut denom = F::ONE;
|
||||
for l in included {
|
||||
if i == *l {
|
||||
continue;
|
||||
impl<F: Zeroize + PrimeField> Interpolation<F> {
|
||||
pub(crate) fn interpolation_factor(&self, i: Participant, included: &[Participant]) -> F {
|
||||
match self {
|
||||
Interpolation::Constant(c) => c[usize::from(u16::from(i) - 1)],
|
||||
Interpolation::Lagrange => {
|
||||
let i_f = F::from(u64::from(u16::from(i)));
|
||||
|
||||
let mut num = F::ONE;
|
||||
let mut denom = F::ONE;
|
||||
for l in included {
|
||||
if i == *l {
|
||||
continue;
|
||||
}
|
||||
|
||||
let share = F::from(u64::from(u16::from(*l)));
|
||||
num *= share;
|
||||
denom *= share - i_f;
|
||||
}
|
||||
|
||||
// Safe as this will only be 0 if we're part of the above loop
|
||||
// (which we have an if case to avoid)
|
||||
num * denom.invert().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
let share = F::from(u64::from(u16::from(*l)));
|
||||
num *= share;
|
||||
denom *= share - i_f;
|
||||
}
|
||||
|
||||
// Safe as this will only be 0 if we're part of the above loop
|
||||
// (which we have an if case to avoid)
|
||||
num * denom.invert().unwrap()
|
||||
}
|
||||
|
||||
/// Keys and verification shares generated by a DKG.
|
||||
@@ -232,6 +242,8 @@ mod lib {
|
||||
pub struct ThresholdCore<C: Ciphersuite> {
|
||||
/// Threshold Parameters.
|
||||
pub(crate) params: ThresholdParams,
|
||||
/// The interpolation method used.
|
||||
pub(crate) interpolation: Interpolation<C::F>,
|
||||
|
||||
/// Secret share key.
|
||||
pub(crate) secret_share: Zeroizing<C::F>,
|
||||
@@ -246,6 +258,7 @@ mod lib {
|
||||
fmt
|
||||
.debug_struct("ThresholdCore")
|
||||
.field("params", &self.params)
|
||||
.field("interpolation", &self.interpolation)
|
||||
.field("group_key", &self.group_key)
|
||||
.field("verification_shares", &self.verification_shares)
|
||||
.finish_non_exhaustive()
|
||||
@@ -255,6 +268,7 @@ mod lib {
|
||||
impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
|
||||
fn zeroize(&mut self) {
|
||||
self.params.zeroize();
|
||||
self.interpolation.zeroize();
|
||||
self.secret_share.zeroize();
|
||||
self.group_key.zeroize();
|
||||
for share in self.verification_shares.values_mut() {
|
||||
@@ -266,16 +280,14 @@ mod lib {
|
||||
impl<C: Ciphersuite> ThresholdCore<C> {
|
||||
pub(crate) fn new(
|
||||
params: ThresholdParams,
|
||||
interpolation: Interpolation<C::F>,
|
||||
secret_share: Zeroizing<C::F>,
|
||||
verification_shares: HashMap<Participant, C::G>,
|
||||
) -> ThresholdCore<C> {
|
||||
let t = (1 ..= params.t()).map(Participant).collect::<Vec<_>>();
|
||||
ThresholdCore {
|
||||
params,
|
||||
secret_share,
|
||||
group_key: t.iter().map(|i| verification_shares[i] * lagrange::<C::F>(*i, &t)).sum(),
|
||||
verification_shares,
|
||||
}
|
||||
let group_key =
|
||||
t.iter().map(|i| verification_shares[i] * interpolation.interpolation_factor(*i, &t)).sum();
|
||||
ThresholdCore { params, interpolation, secret_share, group_key, verification_shares }
|
||||
}
|
||||
|
||||
/// Parameters for these keys.
|
||||
@@ -304,6 +316,15 @@ mod lib {
|
||||
writer.write_all(&self.params.t.to_le_bytes())?;
|
||||
writer.write_all(&self.params.n.to_le_bytes())?;
|
||||
writer.write_all(&self.params.i.to_bytes())?;
|
||||
match &self.interpolation {
|
||||
Interpolation::Constant(c) => {
|
||||
writer.write_all(&[0])?;
|
||||
for c in c {
|
||||
writer.write_all(c.to_repr().as_ref())?;
|
||||
}
|
||||
}
|
||||
Interpolation::Lagrange => writer.write_all(&[1])?,
|
||||
};
|
||||
let mut share_bytes = self.secret_share.to_repr();
|
||||
writer.write_all(share_bytes.as_ref())?;
|
||||
share_bytes.as_mut().zeroize();
|
||||
@@ -352,6 +373,20 @@ mod lib {
|
||||
)
|
||||
};
|
||||
|
||||
let mut interpolation = [0];
|
||||
reader.read_exact(&mut interpolation)?;
|
||||
let interpolation = match interpolation[0] {
|
||||
0 => Interpolation::Constant({
|
||||
let mut res = Vec::with_capacity(usize::from(n));
|
||||
for _ in 0 .. n {
|
||||
res.push(C::read_F(reader)?);
|
||||
}
|
||||
res
|
||||
}),
|
||||
1 => Interpolation::Lagrange,
|
||||
_ => Err(io::Error::other("invalid interpolation method"))?,
|
||||
};
|
||||
|
||||
let secret_share = Zeroizing::new(C::read_F(reader)?);
|
||||
|
||||
let mut verification_shares = HashMap::new();
|
||||
@@ -361,6 +396,7 @@ mod lib {
|
||||
|
||||
Ok(ThresholdCore::new(
|
||||
ThresholdParams::new(t, n, i).map_err(|_| io::Error::other("invalid parameters"))?,
|
||||
interpolation,
|
||||
secret_share,
|
||||
verification_shares,
|
||||
))
|
||||
@@ -383,6 +419,7 @@ mod lib {
|
||||
/// View of keys, interpolated and offset for usage.
|
||||
#[derive(Clone)]
|
||||
pub struct ThresholdView<C: Ciphersuite> {
|
||||
interpolation: Interpolation<C::F>,
|
||||
offset: C::F,
|
||||
group_key: C::G,
|
||||
included: Vec<Participant>,
|
||||
@@ -395,6 +432,7 @@ mod lib {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt
|
||||
.debug_struct("ThresholdView")
|
||||
.field("interpolation", &self.interpolation)
|
||||
.field("offset", &self.offset)
|
||||
.field("group_key", &self.group_key)
|
||||
.field("included", &self.included)
|
||||
@@ -480,12 +518,13 @@ mod lib {
|
||||
included.sort();
|
||||
|
||||
let mut secret_share = Zeroizing::new(
|
||||
lagrange::<C::F>(self.params().i(), &included) * self.secret_share().deref(),
|
||||
self.core.interpolation.interpolation_factor(self.params().i(), &included) *
|
||||
self.secret_share().deref(),
|
||||
);
|
||||
|
||||
let mut verification_shares = self.verification_shares();
|
||||
for (i, share) in &mut verification_shares {
|
||||
*share *= lagrange::<C::F>(*i, &included);
|
||||
*share *= self.core.interpolation.interpolation_factor(*i, &included);
|
||||
}
|
||||
|
||||
// The offset is included by adding it to the participant with the lowest ID
|
||||
@@ -496,6 +535,7 @@ mod lib {
|
||||
*verification_shares.get_mut(&included[0]).unwrap() += C::generator() * offset;
|
||||
|
||||
Ok(ThresholdView {
|
||||
interpolation: self.core.interpolation.clone(),
|
||||
offset,
|
||||
group_key: self.group_key(),
|
||||
secret_share,
|
||||
@@ -528,6 +568,14 @@ mod lib {
|
||||
&self.included
|
||||
}
|
||||
|
||||
/// Return the interpolation factor for a signer.
|
||||
pub fn interpolation_factor(&self, participant: Participant) -> Option<C::F> {
|
||||
if !self.included.contains(&participant) {
|
||||
None?
|
||||
}
|
||||
Some(self.interpolation.interpolation_factor(participant, &self.included))
|
||||
}
|
||||
|
||||
/// Return the interpolated, offset secret share.
|
||||
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
||||
&self.secret_share
|
||||
|
||||
@@ -7,8 +7,6 @@ use std_shims::collections::HashMap;
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use ciphersuite::group::ff::Field;
|
||||
use ciphersuite::{
|
||||
group::{Group, GroupEncoding},
|
||||
Ciphersuite,
|
||||
@@ -16,7 +14,7 @@ use ciphersuite::{
|
||||
|
||||
use crate::DkgError;
|
||||
#[cfg(feature = "std")]
|
||||
use crate::{Participant, ThresholdParams, ThresholdCore, lagrange};
|
||||
use crate::{Participant, ThresholdParams, Interpolation, ThresholdCore};
|
||||
|
||||
fn check_keys<C: Ciphersuite>(keys: &[C::G]) -> Result<u16, DkgError<()>> {
|
||||
if keys.is_empty() {
|
||||
@@ -67,6 +65,7 @@ pub fn musig_key<C: Ciphersuite>(context: &[u8], keys: &[C::G]) -> Result<C::G,
|
||||
let transcript = binding_factor_transcript::<C>(context, keys)?;
|
||||
let mut res = C::G::identity();
|
||||
for i in 1 ..= keys_len {
|
||||
// TODO: Calculate this with a multiexp
|
||||
res += keys[usize::from(i - 1)] * binding_factor::<C>(transcript.clone(), i);
|
||||
}
|
||||
Ok(res)
|
||||
@@ -104,38 +103,26 @@ pub fn musig<C: Ciphersuite>(
|
||||
binding.push(binding_factor::<C>(transcript.clone(), i));
|
||||
}
|
||||
|
||||
// Multiply our private key by our binding factor
|
||||
let mut secret_share = private_key.clone();
|
||||
*secret_share *= binding[pos];
|
||||
// Our secret share is our private key
|
||||
let secret_share = private_key.clone();
|
||||
|
||||
// Calculate verification shares
|
||||
let mut verification_shares = HashMap::new();
|
||||
// When this library offers a ThresholdView for a specific signing set, it applies the lagrange
|
||||
// factor
|
||||
// Since this is a n-of-n scheme, there's only one possible signing set, and one possible
|
||||
// lagrange factor
|
||||
// In the name of simplicity, we define the group key as the sum of all bound keys
|
||||
// Accordingly, the secret share must be multiplied by the inverse of the lagrange factor, along
|
||||
// with all verification shares
|
||||
// This is less performant than simply defining the group key as the sum of all post-lagrange
|
||||
// bound keys, yet the simplicity is preferred
|
||||
let included = (1 ..= keys_len)
|
||||
// This error also shouldn't be possible, for the same reasons as documented above
|
||||
.map(|l| Participant::new(l).ok_or(DkgError::InvalidSigningSet))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut group_key = C::G::identity();
|
||||
for (l, p) in included.iter().enumerate() {
|
||||
let bound = keys[l] * binding[l];
|
||||
group_key += bound;
|
||||
for l in 1 ..= keys_len {
|
||||
let key = keys[usize::from(l) - 1];
|
||||
group_key += key * binding[usize::from(l - 1)];
|
||||
|
||||
let lagrange_inv = lagrange::<C::F>(*p, &included).invert().unwrap();
|
||||
if params.i() == *p {
|
||||
*secret_share *= lagrange_inv;
|
||||
}
|
||||
verification_shares.insert(*p, bound * lagrange_inv);
|
||||
// These errors also shouldn't be possible, for the same reasons as documented above
|
||||
verification_shares.insert(Participant::new(l).ok_or(DkgError::InvalidSigningSet)?, key);
|
||||
}
|
||||
debug_assert_eq!(C::generator() * secret_share.deref(), verification_shares[¶ms.i()]);
|
||||
debug_assert_eq!(musig_key::<C>(context, keys).unwrap(), group_key);
|
||||
|
||||
Ok(ThresholdCore { params, secret_share, group_key, verification_shares })
|
||||
Ok(ThresholdCore::new(
|
||||
params,
|
||||
Interpolation::Constant(binding),
|
||||
secret_share,
|
||||
verification_shares,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ use multiexp::{multiexp_vartime, BatchVerifier};
|
||||
use schnorr::SchnorrSignature;
|
||||
|
||||
use crate::{
|
||||
Participant, DkgError, ThresholdParams, ThresholdCore, validate_map,
|
||||
Participant, DkgError, ThresholdParams, Interpolation, ThresholdCore, validate_map,
|
||||
encryption::{
|
||||
ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption, EncryptionKeyProof,
|
||||
ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption, Decryption, EncryptionKeyProof,
|
||||
DecryptionError,
|
||||
},
|
||||
};
|
||||
@@ -32,10 +32,10 @@ use crate::{
|
||||
type FrostError<C> = DkgError<EncryptionKeyProof<C>>;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn challenge<C: Ciphersuite>(context: &str, l: Participant, R: &[u8], Am: &[u8]) -> C::F {
|
||||
fn challenge<C: Ciphersuite>(context: [u8; 32], l: Participant, R: &[u8], Am: &[u8]) -> C::F {
|
||||
let mut transcript = RecommendedTranscript::new(b"DKG FROST v0.2");
|
||||
transcript.domain_separate(b"schnorr_proof_of_knowledge");
|
||||
transcript.append_message(b"context", context.as_bytes());
|
||||
transcript.append_message(b"context", context);
|
||||
transcript.append_message(b"participant", l.to_bytes());
|
||||
transcript.append_message(b"nonce", R);
|
||||
transcript.append_message(b"commitments", Am);
|
||||
@@ -86,15 +86,15 @@ impl<C: Ciphersuite> ReadWrite for Commitments<C> {
|
||||
#[derive(Debug, Zeroize)]
|
||||
pub struct KeyGenMachine<C: Ciphersuite> {
|
||||
params: ThresholdParams,
|
||||
context: String,
|
||||
context: [u8; 32],
|
||||
_curve: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> KeyGenMachine<C> {
|
||||
/// Create a new machine to generate a key.
|
||||
///
|
||||
/// The context string should be unique among multisigs.
|
||||
pub fn new(params: ThresholdParams, context: String) -> KeyGenMachine<C> {
|
||||
/// The context should be unique among multisigs.
|
||||
pub fn new(params: ThresholdParams, context: [u8; 32]) -> KeyGenMachine<C> {
|
||||
KeyGenMachine { params, context, _curve: PhantomData }
|
||||
}
|
||||
|
||||
@@ -129,11 +129,11 @@ impl<C: Ciphersuite> KeyGenMachine<C> {
|
||||
// There's no reason to spend the time and effort to make this deterministic besides a
|
||||
// general obsession with canonicity and determinism though
|
||||
r,
|
||||
challenge::<C>(&self.context, self.params.i(), nonce.to_bytes().as_ref(), &cached_msg),
|
||||
challenge::<C>(self.context, self.params.i(), nonce.to_bytes().as_ref(), &cached_msg),
|
||||
);
|
||||
|
||||
// Additionally create an encryption mechanism to protect the secret shares
|
||||
let encryption = Encryption::new(self.context.clone(), Some(self.params.i), rng);
|
||||
let encryption = Encryption::new(self.context, self.params.i, rng);
|
||||
|
||||
// Step 4: Broadcast
|
||||
let msg =
|
||||
@@ -225,7 +225,7 @@ impl<F: PrimeField> ReadWrite for SecretShare<F> {
|
||||
#[derive(Zeroize)]
|
||||
pub struct SecretShareMachine<C: Ciphersuite> {
|
||||
params: ThresholdParams,
|
||||
context: String,
|
||||
context: [u8; 32],
|
||||
coefficients: Vec<Zeroizing<C::F>>,
|
||||
our_commitments: Vec<C::G>,
|
||||
encryption: Encryption<C>,
|
||||
@@ -274,7 +274,7 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
||||
&mut batch,
|
||||
l,
|
||||
msg.commitments[0],
|
||||
challenge::<C>(&self.context, l, msg.sig.R.to_bytes().as_ref(), &msg.cached_msg),
|
||||
challenge::<C>(self.context, l, msg.sig.R.to_bytes().as_ref(), &msg.cached_msg),
|
||||
);
|
||||
|
||||
commitments.insert(l, msg.commitments.drain(..).collect::<Vec<_>>());
|
||||
@@ -472,9 +472,10 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
||||
let KeyMachine { commitments, encryption, params, secret } = self;
|
||||
Ok(BlameMachine {
|
||||
commitments,
|
||||
encryption,
|
||||
encryption: encryption.into_decryption(),
|
||||
result: Some(ThresholdCore {
|
||||
params,
|
||||
interpolation: Interpolation::Lagrange,
|
||||
secret_share: secret,
|
||||
group_key: stripes[0],
|
||||
verification_shares,
|
||||
@@ -486,7 +487,7 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
||||
/// A machine capable of handling blame proofs.
|
||||
pub struct BlameMachine<C: Ciphersuite> {
|
||||
commitments: HashMap<Participant, Vec<C::G>>,
|
||||
encryption: Encryption<C>,
|
||||
encryption: Decryption<C>,
|
||||
result: Option<ThresholdCore<C>>,
|
||||
}
|
||||
|
||||
@@ -505,7 +506,6 @@ impl<C: Ciphersuite> Zeroize for BlameMachine<C> {
|
||||
for commitments in self.commitments.values_mut() {
|
||||
commitments.zeroize();
|
||||
}
|
||||
self.encryption.zeroize();
|
||||
self.result.zeroize();
|
||||
}
|
||||
}
|
||||
@@ -598,14 +598,13 @@ impl<C: Ciphersuite> AdditionalBlameMachine<C> {
|
||||
/// authenticated as having come from the supposed party and verified as valid. Usage of invalid
|
||||
/// commitments is considered undefined behavior, and may cause everything from inaccurate blame
|
||||
/// to panics.
|
||||
pub fn new<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
context: String,
|
||||
pub fn new(
|
||||
context: [u8; 32],
|
||||
n: u16,
|
||||
mut commitment_msgs: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
|
||||
) -> Result<Self, FrostError<C>> {
|
||||
let mut commitments = HashMap::new();
|
||||
let mut encryption = Encryption::new(context, None, rng);
|
||||
let mut encryption = Decryption::new(context);
|
||||
for i in 1 ..= n {
|
||||
let i = Participant::new(i).unwrap();
|
||||
let Some(msg) = commitment_msgs.remove(&i) else { Err(DkgError::MissingParticipant(i))? };
|
||||
|
||||
@@ -113,6 +113,7 @@ impl<C1: Ciphersuite, C2: Ciphersuite<F = C1::F, G = C1::G>> GeneratorPromotion<
|
||||
Ok(ThresholdKeys {
|
||||
core: Arc::new(ThresholdCore::new(
|
||||
params,
|
||||
self.base.core.interpolation.clone(),
|
||||
self.base.secret_share().clone(),
|
||||
verification_shares,
|
||||
)),
|
||||
|
||||
@@ -6,7 +6,7 @@ use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite};
|
||||
|
||||
use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange, musig::musig as musig_fn};
|
||||
use crate::{Participant, ThresholdCore, ThresholdKeys, musig::musig as musig_fn};
|
||||
|
||||
mod musig;
|
||||
pub use musig::test_musig;
|
||||
@@ -43,7 +43,8 @@ pub fn recover_key<C: Ciphersuite>(keys: &HashMap<Participant, ThresholdKeys<C>>
|
||||
let included = keys.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
let group_private = keys.iter().fold(C::F::ZERO, |accum, (i, keys)| {
|
||||
accum + (lagrange::<C::F>(*i, &included) * keys.secret_share().deref())
|
||||
accum +
|
||||
(first.core.interpolation.interpolation_factor(*i, &included) * keys.secret_share().deref())
|
||||
});
|
||||
assert_eq!(C::generator() * group_private, first.group_key(), "failed to recover keys");
|
||||
group_private
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
type PedPoPEncryptedMessage<C> = EncryptedMessage<C, SecretShare<<C as Ciphersuite>::F>>;
|
||||
type PedPoPSecretShares<C> = HashMap<Participant, PedPoPEncryptedMessage<C>>;
|
||||
|
||||
const CONTEXT: &str = "DKG Test Key Generation";
|
||||
const CONTEXT: [u8; 32] = *b"DKG Test Key Generation ";
|
||||
|
||||
// Commit, then return commitment messages, enc keys, and shares
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -31,7 +31,7 @@ fn commit_enc_keys_and_shares<R: RngCore + CryptoRng, C: Ciphersuite>(
|
||||
let mut enc_keys = HashMap::new();
|
||||
for i in (1 ..= PARTICIPANTS).map(Participant) {
|
||||
let params = ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap();
|
||||
let machine = KeyGenMachine::<C>::new(params, CONTEXT.to_string());
|
||||
let machine = KeyGenMachine::<C>::new(params, CONTEXT);
|
||||
let (machine, these_commitments) = machine.generate_coefficients(rng);
|
||||
machines.insert(i, machine);
|
||||
|
||||
@@ -147,14 +147,12 @@ mod literal {
|
||||
|
||||
// Verify machines constructed with AdditionalBlameMachine::new work
|
||||
assert_eq!(
|
||||
AdditionalBlameMachine::new(
|
||||
&mut OsRng,
|
||||
CONTEXT.to_string(),
|
||||
PARTICIPANTS,
|
||||
commitment_msgs.clone()
|
||||
)
|
||||
.unwrap()
|
||||
.blame(ONE, TWO, msg.clone(), blame.clone()),
|
||||
AdditionalBlameMachine::new(CONTEXT, PARTICIPANTS, commitment_msgs.clone()).unwrap().blame(
|
||||
ONE,
|
||||
TWO,
|
||||
msg.clone(),
|
||||
blame.clone()
|
||||
),
|
||||
ONE,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ impl<C: Ciphersuite> Ciphersuite for AltGenerator<C> {
|
||||
C::G::generator() * <C as Ciphersuite>::hash_to_F(b"DKG Promotion Test", b"generator")
|
||||
}
|
||||
|
||||
fn reduce_512(scalar: [u8; 64]) -> Self::F {
|
||||
<C as Ciphersuite>::reduce_512(scalar)
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
<C as Ciphersuite>::hash_to_F(dst, data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user