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/ciphersuite
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["ciphersuite", "ff", "group"]
|
keywords = ["ciphersuite", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.74"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ macro_rules! dalek_curve {
|
|||||||
$Point::generator()
|
$Point::generator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reduce_512(mut scalar: [u8; 64]) -> Self::F {
|
||||||
|
let res = Scalar::from_bytes_mod_order_wide(&scalar);
|
||||||
|
scalar.zeroize();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||||
Scalar::from_hash(Sha512::new_with_prefix(&[dst, data].concat()))
|
Scalar::from_hash(Sha512::new_with_prefix(&[dst, data].concat()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ impl Ciphersuite for Ed448 {
|
|||||||
Point::generator()
|
Point::generator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reduce_512(mut scalar: [u8; 64]) -> Self::F {
|
||||||
|
let res = Self::hash_to_F(b"Ciphersuite-reduce_512", &scalar);
|
||||||
|
scalar.zeroize();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||||
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap())
|
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use group::ff::PrimeField;
|
|||||||
|
|
||||||
use elliptic_curve::{
|
use elliptic_curve::{
|
||||||
generic_array::GenericArray,
|
generic_array::GenericArray,
|
||||||
bigint::{NonZero, CheckedAdd, Encoding, U384},
|
bigint::{NonZero, CheckedAdd, Encoding, U384, U512},
|
||||||
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
|
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,6 +31,22 @@ macro_rules! kp_curve {
|
|||||||
$lib::ProjectivePoint::GENERATOR
|
$lib::ProjectivePoint::GENERATOR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reduce_512(scalar: [u8; 64]) -> Self::F {
|
||||||
|
let mut modulus = [0; 64];
|
||||||
|
modulus[32 ..].copy_from_slice(&(Self::F::ZERO - Self::F::ONE).to_bytes());
|
||||||
|
let modulus = U512::from_be_slice(&modulus).checked_add(&U512::ONE).unwrap();
|
||||||
|
|
||||||
|
let mut wide =
|
||||||
|
U512::from_be_bytes(scalar).rem(&NonZero::new(modulus).unwrap()).to_be_bytes();
|
||||||
|
|
||||||
|
let mut array = *GenericArray::from_slice(&wide[32 ..]);
|
||||||
|
let res = $lib::Scalar::from_repr(array).unwrap();
|
||||||
|
|
||||||
|
wide.zeroize();
|
||||||
|
array.zeroize();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||||
// While one of these two libraries does support directly hashing to the Scalar field, the
|
// While one of these two libraries does support directly hashing to the Scalar field, the
|
||||||
// other doesn't. While that's probably an oversight, this is a universally working method
|
// other doesn't. While that's probably an oversight, this is a universally working method
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ pub trait Ciphersuite:
|
|||||||
// While group does provide this in its API, privacy coins may want to use a custom basepoint
|
// While group does provide this in its API, privacy coins may want to use a custom basepoint
|
||||||
fn generator() -> Self::G;
|
fn generator() -> Self::G;
|
||||||
|
|
||||||
|
/// Reduce 512 bits into a uniform scalar.
|
||||||
|
///
|
||||||
|
/// If 512 bits is insufficient to perform a reduction into a uniform scalar, the ciphersuite
|
||||||
|
/// will perform a hash to sample the necessary bits.
|
||||||
|
fn reduce_512(scalar: [u8; 64]) -> Self::F;
|
||||||
|
|
||||||
/// Hash the provided domain-separation tag and message to a scalar. Ciphersuites MAY naively
|
/// Hash the provided domain-separation tag and message to a scalar. Ciphersuites MAY naively
|
||||||
/// prefix the tag to the message, enabling transpotion between the two. Accordingly, this
|
/// prefix the tag to the message, enabling transpotion between the two. Accordingly, this
|
||||||
/// function should NOT be used in any scheme where one tag is a valid substring of another
|
/// function should NOT be used in any scheme where one tag is a valid substring of another
|
||||||
@@ -99,6 +105,9 @@ pub trait Ciphersuite:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read a canonical point from something implementing std::io::Read.
|
/// Read a canonical point from something implementing std::io::Read.
|
||||||
|
///
|
||||||
|
/// The provided implementation is safe so long as `GroupEncoding::to_bytes` always returns a
|
||||||
|
/// canonical serialization.
|
||||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
|
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dalek-ff-gr
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
|
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.66"
|
rust-version = "1.71"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl_modulus!(
|
|||||||
type ResidueType = Residue<FieldModulus, { FieldModulus::LIMBS }>;
|
type ResidueType = Residue<FieldModulus, { FieldModulus::LIMBS }>;
|
||||||
|
|
||||||
/// A constant-time implementation of the Ed25519 field.
|
/// A constant-time implementation of the Ed25519 field.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
|
||||||
pub struct FieldElement(ResidueType);
|
pub struct FieldElement(ResidueType);
|
||||||
|
|
||||||
// Square root of -1.
|
// Square root of -1.
|
||||||
@@ -92,7 +92,7 @@ impl Neg for FieldElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Neg for &'a FieldElement {
|
impl Neg for &FieldElement {
|
||||||
type Output = FieldElement;
|
type Output = FieldElement;
|
||||||
fn neg(self) -> Self::Output {
|
fn neg(self) -> Self::Output {
|
||||||
(*self).neg()
|
(*self).neg()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg"
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.79"
|
rust-version = "1.81"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { version = "1", default-features = false, optional = true }
|
thiserror = { version = "2", default-features = false }
|
||||||
|
|
||||||
rand_core = { version = "0.6", default-features = false }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ ciphersuite = { path = "../ciphersuite", default-features = false, features = ["
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
"thiserror",
|
"thiserror/std",
|
||||||
|
|
||||||
"rand_core/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
|
// Each ecdh must be distinct. Reuse of an ecdh for multiple ciphers will cause the messages to be
|
||||||
// leaked.
|
// 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
|
// Ideally, we'd box this transcript with ZAlloc, yet that's only possible on nightly
|
||||||
// TODO: https://github.com/serai-dex/serai/issues/151
|
// TODO: https://github.com/serai-dex/serai/issues/151
|
||||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption v0.2");
|
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");
|
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>(
|
fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
context: &str,
|
context: [u8; 32],
|
||||||
from: Participant,
|
from: Participant,
|
||||||
to: C::G,
|
to: C::G,
|
||||||
mut msg: Zeroizing<E>,
|
mut msg: Zeroizing<E>,
|
||||||
@@ -197,7 +197,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
|||||||
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(
|
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
context: &str,
|
context: [u8; 32],
|
||||||
from: Participant,
|
from: Participant,
|
||||||
) {
|
) {
|
||||||
// Invalidate the message by specifying a new key/Schnorr PoP
|
// 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>(
|
pub(crate) fn invalidate_share_serialization<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
context: &str,
|
context: [u8; 32],
|
||||||
from: Participant,
|
from: Participant,
|
||||||
to: C::G,
|
to: C::G,
|
||||||
) {
|
) {
|
||||||
@@ -243,7 +243,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
|||||||
pub(crate) fn invalidate_share_value<R: RngCore + CryptoRng>(
|
pub(crate) fn invalidate_share_value<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
context: &str,
|
context: [u8; 32],
|
||||||
from: Participant,
|
from: Participant,
|
||||||
to: C::G,
|
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
|
// 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.
|
// root of trust other than their existence in the assumed-to-exist external authenticated channel.
|
||||||
fn pop_challenge<C: Ciphersuite>(
|
fn pop_challenge<C: Ciphersuite>(
|
||||||
context: &str,
|
context: [u8; 32],
|
||||||
nonce: C::G,
|
nonce: C::G,
|
||||||
key: C::G,
|
key: C::G,
|
||||||
sender: Participant,
|
sender: Participant,
|
||||||
msg: &[u8],
|
msg: &[u8],
|
||||||
) -> C::F {
|
) -> C::F {
|
||||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Proof of Possession v0.2");
|
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");
|
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"))
|
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");
|
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
|
transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,58 +337,17 @@ pub(crate) enum DecryptionError {
|
|||||||
InvalidProof,
|
InvalidProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A simple box for managing encryption.
|
// A simple box for managing decryption.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Encryption<C: Ciphersuite> {
|
pub(crate) struct Decryption<C: Ciphersuite> {
|
||||||
context: String,
|
context: [u8; 32],
|
||||||
i: Option<Participant>,
|
|
||||||
enc_key: Zeroizing<C::F>,
|
|
||||||
enc_pub_key: C::G,
|
|
||||||
enc_keys: HashMap<Participant, C::G>,
|
enc_keys: HashMap<Participant, C::G>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Ciphersuite> fmt::Debug for Encryption<C> {
|
impl<C: Ciphersuite> Decryption<C> {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
pub(crate) fn new(context: [u8; 32]) -> Self {
|
||||||
fmt
|
Self { context, enc_keys: HashMap::new() }
|
||||||
.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> 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>(
|
pub(crate) fn register<M: Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
participant: Participant,
|
participant: Participant,
|
||||||
@@ -402,13 +361,109 @@ impl<C: Ciphersuite> Encryption<C> {
|
|||||||
msg.msg
|
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>(
|
pub(crate) fn encrypt<R: RngCore + CryptoRng, E: Encryptable>(
|
||||||
&self,
|
&self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
participant: Participant,
|
participant: Participant,
|
||||||
msg: Zeroizing<E>,
|
msg: Zeroizing<E>,
|
||||||
) -> EncryptedMessage<C, 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>(
|
pub(crate) fn decrypt<R: RngCore + CryptoRng, I: Copy + Zeroize, E: Encryptable>(
|
||||||
@@ -426,18 +481,18 @@ impl<C: Ciphersuite> Encryption<C> {
|
|||||||
batch,
|
batch,
|
||||||
batch_id,
|
batch_id,
|
||||||
msg.key,
|
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);
|
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,
|
msg.msg,
|
||||||
EncryptionKeyProof {
|
EncryptionKeyProof {
|
||||||
key,
|
key,
|
||||||
dleq: DLEqProof::prove(
|
dleq: DLEqProof::prove(
|
||||||
rng,
|
rng,
|
||||||
&mut encryption_key_transcript(&self.context),
|
&mut encryption_key_transcript(self.context),
|
||||||
&[C::generator(), msg.key],
|
&[C::generator(), msg.key],
|
||||||
&self.enc_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.
|
pub(crate) fn into_decryption(self) -> Decryption<C> {
|
||||||
// Returns None if the key was wrong.
|
self.decryption
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use core::fmt::{self, Debug};
|
use core::fmt::{self, Debug};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
@@ -63,8 +62,7 @@ impl fmt::Display for Participant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Various errors possible during key generation.
|
/// Various errors possible during key generation.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
||||||
#[cfg_attr(feature = "std", derive(Error))]
|
|
||||||
pub enum DkgError<B: Clone + PartialEq + Eq + Debug> {
|
pub enum DkgError<B: Clone + PartialEq + Eq + Debug> {
|
||||||
/// A parameter was zero.
|
/// A parameter was zero.
|
||||||
#[cfg_attr(feature = "std", error("a parameter was 0 (threshold {0}, participants {1})"))]
|
#[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.
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub fn lagrange<F: PrimeField>(i: Participant, included: &[Participant]) -> F {
|
pub(crate) enum Interpolation<F: Zeroize + PrimeField> {
|
||||||
let i_f = F::from(u64::from(u16::from(i)));
|
Constant(Vec<F>),
|
||||||
|
Lagrange,
|
||||||
|
}
|
||||||
|
|
||||||
let mut num = F::ONE;
|
impl<F: Zeroize + PrimeField> Interpolation<F> {
|
||||||
let mut denom = F::ONE;
|
pub(crate) fn interpolation_factor(&self, i: Participant, included: &[Participant]) -> F {
|
||||||
for l in included {
|
match self {
|
||||||
if i == *l {
|
Interpolation::Constant(c) => c[usize::from(u16::from(i) - 1)],
|
||||||
continue;
|
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.
|
/// Keys and verification shares generated by a DKG.
|
||||||
@@ -232,6 +242,8 @@ mod lib {
|
|||||||
pub struct ThresholdCore<C: Ciphersuite> {
|
pub struct ThresholdCore<C: Ciphersuite> {
|
||||||
/// Threshold Parameters.
|
/// Threshold Parameters.
|
||||||
pub(crate) params: ThresholdParams,
|
pub(crate) params: ThresholdParams,
|
||||||
|
/// The interpolation method used.
|
||||||
|
pub(crate) interpolation: Interpolation<C::F>,
|
||||||
|
|
||||||
/// Secret share key.
|
/// Secret share key.
|
||||||
pub(crate) secret_share: Zeroizing<C::F>,
|
pub(crate) secret_share: Zeroizing<C::F>,
|
||||||
@@ -246,6 +258,7 @@ mod lib {
|
|||||||
fmt
|
fmt
|
||||||
.debug_struct("ThresholdCore")
|
.debug_struct("ThresholdCore")
|
||||||
.field("params", &self.params)
|
.field("params", &self.params)
|
||||||
|
.field("interpolation", &self.interpolation)
|
||||||
.field("group_key", &self.group_key)
|
.field("group_key", &self.group_key)
|
||||||
.field("verification_shares", &self.verification_shares)
|
.field("verification_shares", &self.verification_shares)
|
||||||
.finish_non_exhaustive()
|
.finish_non_exhaustive()
|
||||||
@@ -255,6 +268,7 @@ mod lib {
|
|||||||
impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
|
impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
|
||||||
fn zeroize(&mut self) {
|
fn zeroize(&mut self) {
|
||||||
self.params.zeroize();
|
self.params.zeroize();
|
||||||
|
self.interpolation.zeroize();
|
||||||
self.secret_share.zeroize();
|
self.secret_share.zeroize();
|
||||||
self.group_key.zeroize();
|
self.group_key.zeroize();
|
||||||
for share in self.verification_shares.values_mut() {
|
for share in self.verification_shares.values_mut() {
|
||||||
@@ -266,16 +280,14 @@ mod lib {
|
|||||||
impl<C: Ciphersuite> ThresholdCore<C> {
|
impl<C: Ciphersuite> ThresholdCore<C> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
params: ThresholdParams,
|
params: ThresholdParams,
|
||||||
|
interpolation: Interpolation<C::F>,
|
||||||
secret_share: Zeroizing<C::F>,
|
secret_share: Zeroizing<C::F>,
|
||||||
verification_shares: HashMap<Participant, C::G>,
|
verification_shares: HashMap<Participant, C::G>,
|
||||||
) -> ThresholdCore<C> {
|
) -> ThresholdCore<C> {
|
||||||
let t = (1 ..= params.t()).map(Participant).collect::<Vec<_>>();
|
let t = (1 ..= params.t()).map(Participant).collect::<Vec<_>>();
|
||||||
ThresholdCore {
|
let group_key =
|
||||||
params,
|
t.iter().map(|i| verification_shares[i] * interpolation.interpolation_factor(*i, &t)).sum();
|
||||||
secret_share,
|
ThresholdCore { params, interpolation, secret_share, group_key, verification_shares }
|
||||||
group_key: t.iter().map(|i| verification_shares[i] * lagrange::<C::F>(*i, &t)).sum(),
|
|
||||||
verification_shares,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameters for these keys.
|
/// Parameters for these keys.
|
||||||
@@ -304,6 +316,15 @@ mod lib {
|
|||||||
writer.write_all(&self.params.t.to_le_bytes())?;
|
writer.write_all(&self.params.t.to_le_bytes())?;
|
||||||
writer.write_all(&self.params.n.to_le_bytes())?;
|
writer.write_all(&self.params.n.to_le_bytes())?;
|
||||||
writer.write_all(&self.params.i.to_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();
|
let mut share_bytes = self.secret_share.to_repr();
|
||||||
writer.write_all(share_bytes.as_ref())?;
|
writer.write_all(share_bytes.as_ref())?;
|
||||||
share_bytes.as_mut().zeroize();
|
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 secret_share = Zeroizing::new(C::read_F(reader)?);
|
||||||
|
|
||||||
let mut verification_shares = HashMap::new();
|
let mut verification_shares = HashMap::new();
|
||||||
@@ -361,6 +396,7 @@ mod lib {
|
|||||||
|
|
||||||
Ok(ThresholdCore::new(
|
Ok(ThresholdCore::new(
|
||||||
ThresholdParams::new(t, n, i).map_err(|_| io::Error::other("invalid parameters"))?,
|
ThresholdParams::new(t, n, i).map_err(|_| io::Error::other("invalid parameters"))?,
|
||||||
|
interpolation,
|
||||||
secret_share,
|
secret_share,
|
||||||
verification_shares,
|
verification_shares,
|
||||||
))
|
))
|
||||||
@@ -383,6 +419,7 @@ mod lib {
|
|||||||
/// View of keys, interpolated and offset for usage.
|
/// View of keys, interpolated and offset for usage.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ThresholdView<C: Ciphersuite> {
|
pub struct ThresholdView<C: Ciphersuite> {
|
||||||
|
interpolation: Interpolation<C::F>,
|
||||||
offset: C::F,
|
offset: C::F,
|
||||||
group_key: C::G,
|
group_key: C::G,
|
||||||
included: Vec<Participant>,
|
included: Vec<Participant>,
|
||||||
@@ -395,6 +432,7 @@ mod lib {
|
|||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt
|
fmt
|
||||||
.debug_struct("ThresholdView")
|
.debug_struct("ThresholdView")
|
||||||
|
.field("interpolation", &self.interpolation)
|
||||||
.field("offset", &self.offset)
|
.field("offset", &self.offset)
|
||||||
.field("group_key", &self.group_key)
|
.field("group_key", &self.group_key)
|
||||||
.field("included", &self.included)
|
.field("included", &self.included)
|
||||||
@@ -480,12 +518,13 @@ mod lib {
|
|||||||
included.sort();
|
included.sort();
|
||||||
|
|
||||||
let mut secret_share = Zeroizing::new(
|
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();
|
let mut verification_shares = self.verification_shares();
|
||||||
for (i, share) in &mut 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
|
// 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;
|
*verification_shares.get_mut(&included[0]).unwrap() += C::generator() * offset;
|
||||||
|
|
||||||
Ok(ThresholdView {
|
Ok(ThresholdView {
|
||||||
|
interpolation: self.core.interpolation.clone(),
|
||||||
offset,
|
offset,
|
||||||
group_key: self.group_key(),
|
group_key: self.group_key(),
|
||||||
secret_share,
|
secret_share,
|
||||||
@@ -528,6 +568,14 @@ mod lib {
|
|||||||
&self.included
|
&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.
|
/// Return the interpolated, offset secret share.
|
||||||
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
||||||
&self.secret_share
|
&self.secret_share
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ use std_shims::collections::HashMap;
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use ciphersuite::group::ff::Field;
|
|
||||||
use ciphersuite::{
|
use ciphersuite::{
|
||||||
group::{Group, GroupEncoding},
|
group::{Group, GroupEncoding},
|
||||||
Ciphersuite,
|
Ciphersuite,
|
||||||
@@ -16,7 +14,7 @@ use ciphersuite::{
|
|||||||
|
|
||||||
use crate::DkgError;
|
use crate::DkgError;
|
||||||
#[cfg(feature = "std")]
|
#[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<()>> {
|
fn check_keys<C: Ciphersuite>(keys: &[C::G]) -> Result<u16, DkgError<()>> {
|
||||||
if keys.is_empty() {
|
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 transcript = binding_factor_transcript::<C>(context, keys)?;
|
||||||
let mut res = C::G::identity();
|
let mut res = C::G::identity();
|
||||||
for i in 1 ..= keys_len {
|
for i in 1 ..= keys_len {
|
||||||
|
// TODO: Calculate this with a multiexp
|
||||||
res += keys[usize::from(i - 1)] * binding_factor::<C>(transcript.clone(), i);
|
res += keys[usize::from(i - 1)] * binding_factor::<C>(transcript.clone(), i);
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -104,38 +103,26 @@ pub fn musig<C: Ciphersuite>(
|
|||||||
binding.push(binding_factor::<C>(transcript.clone(), i));
|
binding.push(binding_factor::<C>(transcript.clone(), i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiply our private key by our binding factor
|
// Our secret share is our private key
|
||||||
let mut secret_share = private_key.clone();
|
let secret_share = private_key.clone();
|
||||||
*secret_share *= binding[pos];
|
|
||||||
|
|
||||||
// Calculate verification shares
|
// Calculate verification shares
|
||||||
let mut verification_shares = HashMap::new();
|
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();
|
let mut group_key = C::G::identity();
|
||||||
for (l, p) in included.iter().enumerate() {
|
for l in 1 ..= keys_len {
|
||||||
let bound = keys[l] * binding[l];
|
let key = keys[usize::from(l) - 1];
|
||||||
group_key += bound;
|
group_key += key * binding[usize::from(l - 1)];
|
||||||
|
|
||||||
let lagrange_inv = lagrange::<C::F>(*p, &included).invert().unwrap();
|
// These errors also shouldn't be possible, for the same reasons as documented above
|
||||||
if params.i() == *p {
|
verification_shares.insert(Participant::new(l).ok_or(DkgError::InvalidSigningSet)?, key);
|
||||||
*secret_share *= lagrange_inv;
|
|
||||||
}
|
|
||||||
verification_shares.insert(*p, bound * lagrange_inv);
|
|
||||||
}
|
}
|
||||||
debug_assert_eq!(C::generator() * secret_share.deref(), verification_shares[¶ms.i()]);
|
debug_assert_eq!(C::generator() * secret_share.deref(), verification_shares[¶ms.i()]);
|
||||||
debug_assert_eq!(musig_key::<C>(context, keys).unwrap(), group_key);
|
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 schnorr::SchnorrSignature;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Participant, DkgError, ThresholdParams, ThresholdCore, validate_map,
|
Participant, DkgError, ThresholdParams, Interpolation, ThresholdCore, validate_map,
|
||||||
encryption::{
|
encryption::{
|
||||||
ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption, EncryptionKeyProof,
|
ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption, Decryption, EncryptionKeyProof,
|
||||||
DecryptionError,
|
DecryptionError,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -32,10 +32,10 @@ use crate::{
|
|||||||
type FrostError<C> = DkgError<EncryptionKeyProof<C>>;
|
type FrostError<C> = DkgError<EncryptionKeyProof<C>>;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[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");
|
let mut transcript = RecommendedTranscript::new(b"DKG FROST v0.2");
|
||||||
transcript.domain_separate(b"schnorr_proof_of_knowledge");
|
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"participant", l.to_bytes());
|
||||||
transcript.append_message(b"nonce", R);
|
transcript.append_message(b"nonce", R);
|
||||||
transcript.append_message(b"commitments", Am);
|
transcript.append_message(b"commitments", Am);
|
||||||
@@ -86,15 +86,15 @@ impl<C: Ciphersuite> ReadWrite for Commitments<C> {
|
|||||||
#[derive(Debug, Zeroize)]
|
#[derive(Debug, Zeroize)]
|
||||||
pub struct KeyGenMachine<C: Ciphersuite> {
|
pub struct KeyGenMachine<C: Ciphersuite> {
|
||||||
params: ThresholdParams,
|
params: ThresholdParams,
|
||||||
context: String,
|
context: [u8; 32],
|
||||||
_curve: PhantomData<C>,
|
_curve: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Ciphersuite> KeyGenMachine<C> {
|
impl<C: Ciphersuite> KeyGenMachine<C> {
|
||||||
/// Create a new machine to generate a key.
|
/// Create a new machine to generate a key.
|
||||||
///
|
///
|
||||||
/// The context string should be unique among multisigs.
|
/// The context should be unique among multisigs.
|
||||||
pub fn new(params: ThresholdParams, context: String) -> KeyGenMachine<C> {
|
pub fn new(params: ThresholdParams, context: [u8; 32]) -> KeyGenMachine<C> {
|
||||||
KeyGenMachine { params, context, _curve: PhantomData }
|
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
|
// There's no reason to spend the time and effort to make this deterministic besides a
|
||||||
// general obsession with canonicity and determinism though
|
// general obsession with canonicity and determinism though
|
||||||
r,
|
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
|
// 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
|
// Step 4: Broadcast
|
||||||
let msg =
|
let msg =
|
||||||
@@ -225,7 +225,7 @@ impl<F: PrimeField> ReadWrite for SecretShare<F> {
|
|||||||
#[derive(Zeroize)]
|
#[derive(Zeroize)]
|
||||||
pub struct SecretShareMachine<C: Ciphersuite> {
|
pub struct SecretShareMachine<C: Ciphersuite> {
|
||||||
params: ThresholdParams,
|
params: ThresholdParams,
|
||||||
context: String,
|
context: [u8; 32],
|
||||||
coefficients: Vec<Zeroizing<C::F>>,
|
coefficients: Vec<Zeroizing<C::F>>,
|
||||||
our_commitments: Vec<C::G>,
|
our_commitments: Vec<C::G>,
|
||||||
encryption: Encryption<C>,
|
encryption: Encryption<C>,
|
||||||
@@ -274,7 +274,7 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
|||||||
&mut batch,
|
&mut batch,
|
||||||
l,
|
l,
|
||||||
msg.commitments[0],
|
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<_>>());
|
commitments.insert(l, msg.commitments.drain(..).collect::<Vec<_>>());
|
||||||
@@ -472,9 +472,10 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
|||||||
let KeyMachine { commitments, encryption, params, secret } = self;
|
let KeyMachine { commitments, encryption, params, secret } = self;
|
||||||
Ok(BlameMachine {
|
Ok(BlameMachine {
|
||||||
commitments,
|
commitments,
|
||||||
encryption,
|
encryption: encryption.into_decryption(),
|
||||||
result: Some(ThresholdCore {
|
result: Some(ThresholdCore {
|
||||||
params,
|
params,
|
||||||
|
interpolation: Interpolation::Lagrange,
|
||||||
secret_share: secret,
|
secret_share: secret,
|
||||||
group_key: stripes[0],
|
group_key: stripes[0],
|
||||||
verification_shares,
|
verification_shares,
|
||||||
@@ -486,7 +487,7 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
|||||||
/// A machine capable of handling blame proofs.
|
/// A machine capable of handling blame proofs.
|
||||||
pub struct BlameMachine<C: Ciphersuite> {
|
pub struct BlameMachine<C: Ciphersuite> {
|
||||||
commitments: HashMap<Participant, Vec<C::G>>,
|
commitments: HashMap<Participant, Vec<C::G>>,
|
||||||
encryption: Encryption<C>,
|
encryption: Decryption<C>,
|
||||||
result: Option<ThresholdCore<C>>,
|
result: Option<ThresholdCore<C>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +506,6 @@ impl<C: Ciphersuite> Zeroize for BlameMachine<C> {
|
|||||||
for commitments in self.commitments.values_mut() {
|
for commitments in self.commitments.values_mut() {
|
||||||
commitments.zeroize();
|
commitments.zeroize();
|
||||||
}
|
}
|
||||||
self.encryption.zeroize();
|
|
||||||
self.result.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
|
/// 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
|
/// commitments is considered undefined behavior, and may cause everything from inaccurate blame
|
||||||
/// to panics.
|
/// to panics.
|
||||||
pub fn new<R: RngCore + CryptoRng>(
|
pub fn new(
|
||||||
rng: &mut R,
|
context: [u8; 32],
|
||||||
context: String,
|
|
||||||
n: u16,
|
n: u16,
|
||||||
mut commitment_msgs: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
|
mut commitment_msgs: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
|
||||||
) -> Result<Self, FrostError<C>> {
|
) -> Result<Self, FrostError<C>> {
|
||||||
let mut commitments = HashMap::new();
|
let mut commitments = HashMap::new();
|
||||||
let mut encryption = Encryption::new(context, None, rng);
|
let mut encryption = Decryption::new(context);
|
||||||
for i in 1 ..= n {
|
for i in 1 ..= n {
|
||||||
let i = Participant::new(i).unwrap();
|
let i = Participant::new(i).unwrap();
|
||||||
let Some(msg) = commitment_msgs.remove(&i) else { Err(DkgError::MissingParticipant(i))? };
|
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 {
|
Ok(ThresholdKeys {
|
||||||
core: Arc::new(ThresholdCore::new(
|
core: Arc::new(ThresholdCore::new(
|
||||||
params,
|
params,
|
||||||
|
self.base.core.interpolation.clone(),
|
||||||
self.base.secret_share().clone(),
|
self.base.secret_share().clone(),
|
||||||
verification_shares,
|
verification_shares,
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use rand_core::{RngCore, CryptoRng};
|
|||||||
|
|
||||||
use ciphersuite::{group::ff::Field, Ciphersuite};
|
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;
|
mod musig;
|
||||||
pub use musig::test_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 included = keys.keys().copied().collect::<Vec<_>>();
|
||||||
|
|
||||||
let group_private = keys.iter().fold(C::F::ZERO, |accum, (i, keys)| {
|
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");
|
assert_eq!(C::generator() * group_private, first.group_key(), "failed to recover keys");
|
||||||
group_private
|
group_private
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
type PedPoPEncryptedMessage<C> = EncryptedMessage<C, SecretShare<<C as Ciphersuite>::F>>;
|
type PedPoPEncryptedMessage<C> = EncryptedMessage<C, SecretShare<<C as Ciphersuite>::F>>;
|
||||||
type PedPoPSecretShares<C> = HashMap<Participant, PedPoPEncryptedMessage<C>>;
|
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
|
// Commit, then return commitment messages, enc keys, and shares
|
||||||
#[allow(clippy::type_complexity)]
|
#[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();
|
let mut enc_keys = HashMap::new();
|
||||||
for i in (1 ..= PARTICIPANTS).map(Participant) {
|
for i in (1 ..= PARTICIPANTS).map(Participant) {
|
||||||
let params = ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap();
|
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);
|
let (machine, these_commitments) = machine.generate_coefficients(rng);
|
||||||
machines.insert(i, machine);
|
machines.insert(i, machine);
|
||||||
|
|
||||||
@@ -147,14 +147,12 @@ mod literal {
|
|||||||
|
|
||||||
// Verify machines constructed with AdditionalBlameMachine::new work
|
// Verify machines constructed with AdditionalBlameMachine::new work
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AdditionalBlameMachine::new(
|
AdditionalBlameMachine::new(CONTEXT, PARTICIPANTS, commitment_msgs.clone()).unwrap().blame(
|
||||||
&mut OsRng,
|
ONE,
|
||||||
CONTEXT.to_string(),
|
TWO,
|
||||||
PARTICIPANTS,
|
msg.clone(),
|
||||||
commitment_msgs.clone()
|
blame.clone()
|
||||||
)
|
),
|
||||||
.unwrap()
|
|
||||||
.blame(ONE, TWO, msg.clone(), blame.clone()),
|
|
||||||
ONE,
|
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")
|
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 {
|
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||||
<C as Ciphersuite>::hash_to_F(dst, data)
|
<C as Ciphersuite>::hash_to_F(dst, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ license = "MIT"
|
|||||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dleq"
|
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dleq"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.79"
|
rust-version = "1.81"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
@@ -18,7 +18,7 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
|
|
||||||
thiserror = { version = "1", optional = true }
|
thiserror = { version = "2", default-features = false, optional = true }
|
||||||
rand_core = { version = "0.6", default-features = false }
|
rand_core = { version = "0.6", default-features = false }
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
@@ -44,7 +44,7 @@ dalek-ff-group = { path = "../dalek-ff-group" }
|
|||||||
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"] }
|
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["rand_core/std", "zeroize/std", "digest/std", "transcript/std", "ff/std", "multiexp?/std"]
|
std = ["thiserror?/std", "rand_core/std", "zeroize/std", "digest/std", "transcript/std", "ff/std", "multiexp?/std"]
|
||||||
serialize = ["std"]
|
serialize = ["std"]
|
||||||
|
|
||||||
# Needed for cross-group DLEqs
|
# Needed for cross-group DLEqs
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ impl<G: PrimeGroup> Generators<G> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Error for cross-group DLEq proofs.
|
/// Error for cross-group DLEq proofs.
|
||||||
#[derive(Error, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
||||||
pub enum DLEqError {
|
pub enum DLEqError {
|
||||||
/// Invalid proof length.
|
/// Invalid proof length.
|
||||||
#[error("invalid proof length")]
|
#[error("invalid proof length")]
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ pub(crate) fn challenge<T: Transcript, F: PrimeField>(transcript: &mut T) -> F {
|
|||||||
// Get a wide amount of bytes to safely reduce without bias
|
// Get a wide amount of bytes to safely reduce without bias
|
||||||
// In most cases, <=1.5x bytes is enough. 2x is still standard and there's some theoretical
|
// In most cases, <=1.5x bytes is enough. 2x is still standard and there's some theoretical
|
||||||
// groups which may technically require more than 1.5x bytes for this to work as intended
|
// groups which may technically require more than 1.5x bytes for this to work as intended
|
||||||
let target_bytes = ((usize::try_from(F::NUM_BITS).unwrap() + 7) / 8) * 2;
|
let target_bytes = usize::try_from(F::NUM_BITS).unwrap().div_ceil(8) * 2;
|
||||||
let mut challenge_bytes = transcript.challenge(b"challenge");
|
let mut challenge_bytes = transcript.challenge(b"challenge");
|
||||||
let challenge_bytes_len = challenge_bytes.as_ref().len();
|
let challenge_bytes_len = challenge_bytes.as_ref().len();
|
||||||
// If the challenge is 32 bytes, and we need 64, we need two challenges
|
// If the challenge is 32 bytes, and we need 64, we need two challenges
|
||||||
let needed_challenges = (target_bytes + (challenge_bytes_len - 1)) / challenge_bytes_len;
|
let needed_challenges = target_bytes.div_ceil(challenge_bytes_len);
|
||||||
|
|
||||||
// The following algorithm should be equivalent to a wide reduction of the challenges,
|
// The following algorithm should be equivalent to a wide reduction of the challenges,
|
||||||
// interpreted as concatenated, big-endian byte string
|
// interpreted as concatenated, big-endian byte string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ed448"
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["ed448", "ff", "group"]
|
keywords = ["ed448", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.66"
|
rust-version = "1.71"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -154,18 +154,20 @@ pub fn test_group<R: RngCore, G: Group>(rng: &mut R) {
|
|||||||
|
|
||||||
/// Test encoding and decoding of group elements.
|
/// Test encoding and decoding of group elements.
|
||||||
pub fn test_encoding<G: PrimeGroup>() {
|
pub fn test_encoding<G: PrimeGroup>() {
|
||||||
let test = |point: G, msg| {
|
let test = |point: G, msg| -> G {
|
||||||
let bytes = point.to_bytes();
|
let bytes = point.to_bytes();
|
||||||
let mut repr = G::Repr::default();
|
let mut repr = G::Repr::default();
|
||||||
repr.as_mut().copy_from_slice(bytes.as_ref());
|
repr.as_mut().copy_from_slice(bytes.as_ref());
|
||||||
assert_eq!(point, G::from_bytes(&repr).unwrap(), "{msg} couldn't be encoded and decoded");
|
let decoded = G::from_bytes(&repr).unwrap();
|
||||||
|
assert_eq!(point, decoded, "{msg} couldn't be encoded and decoded");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
point,
|
point,
|
||||||
G::from_bytes_unchecked(&repr).unwrap(),
|
G::from_bytes_unchecked(&repr).unwrap(),
|
||||||
"{msg} couldn't be encoded and decoded",
|
"{msg} couldn't be encoded and decoded",
|
||||||
);
|
);
|
||||||
|
decoded
|
||||||
};
|
};
|
||||||
test(G::identity(), "identity");
|
assert!(bool::from(test(G::identity(), "identity").is_identity()));
|
||||||
test(G::generator(), "generator");
|
test(G::generator(), "generator");
|
||||||
test(G::generator() + G::generator(), "(generator * 2)");
|
test(G::generator() + G::generator(), "(generator * 2)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1"
|
thiserror = { version = "2", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||||
rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
|
rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
|
||||||
|
|||||||
@@ -203,14 +203,15 @@ pub trait SignMachine<S>: Send + Sync + Sized {
|
|||||||
/// SignatureMachine this SignMachine turns into.
|
/// SignatureMachine this SignMachine turns into.
|
||||||
type SignatureMachine: SignatureMachine<S, SignatureShare = Self::SignatureShare>;
|
type SignatureMachine: SignatureMachine<S, SignatureShare = Self::SignatureShare>;
|
||||||
|
|
||||||
/// Cache this preprocess for usage later. This cached preprocess MUST only be used once. Reuse
|
/// Cache this preprocess for usage later.
|
||||||
/// of it enables recovery of your private key share. Third-party recovery of a cached preprocess
|
///
|
||||||
/// also enables recovery of your private key share, so this MUST be treated with the same
|
/// This cached preprocess MUST only be used once. Reuse of it enables recovery of your private
|
||||||
/// security as your private key share.
|
/// key share. Third-party recovery of a cached preprocess also enables recovery of your private
|
||||||
|
/// key share, so this MUST be treated with the same security as your private key share.
|
||||||
fn cache(self) -> CachedPreprocess;
|
fn cache(self) -> CachedPreprocess;
|
||||||
|
|
||||||
/// Create a sign machine from a cached preprocess.
|
/// Create a sign machine from a cached preprocess.
|
||||||
|
///
|
||||||
/// After this, the preprocess must be deleted so it's never reused. Any reuse will presumably
|
/// After this, the preprocess must be deleted so it's never reused. Any reuse will presumably
|
||||||
/// cause the signer to leak their secret share.
|
/// cause the signer to leak their secret share.
|
||||||
fn from_cache(
|
fn from_cache(
|
||||||
@@ -219,11 +220,14 @@ pub trait SignMachine<S>: Send + Sync + Sized {
|
|||||||
cache: CachedPreprocess,
|
cache: CachedPreprocess,
|
||||||
) -> (Self, Self::Preprocess);
|
) -> (Self, Self::Preprocess);
|
||||||
|
|
||||||
/// Read a Preprocess message. Despite taking self, this does not save the preprocess.
|
/// Read a Preprocess message.
|
||||||
/// It must be externally cached and passed into sign.
|
///
|
||||||
|
/// Despite taking self, this does not save the preprocess. It must be externally cached and
|
||||||
|
/// passed into sign.
|
||||||
fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess>;
|
fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess>;
|
||||||
|
|
||||||
/// Sign a message.
|
/// Sign a message.
|
||||||
|
///
|
||||||
/// Takes in the participants' preprocess messages. Returns the signature share to be broadcast
|
/// Takes in the participants' preprocess messages. Returns the signature share to be broadcast
|
||||||
/// to all participants, over an authenticated channel. The parties who participate here will
|
/// to all participants, over an authenticated channel. The parties who participate here will
|
||||||
/// become the signing set for this session.
|
/// become the signing set for this session.
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<Participant,
|
|||||||
serialized.extend(vectors.threshold.to_le_bytes());
|
serialized.extend(vectors.threshold.to_le_bytes());
|
||||||
serialized.extend(u16::try_from(shares.len()).unwrap().to_le_bytes());
|
serialized.extend(u16::try_from(shares.len()).unwrap().to_le_bytes());
|
||||||
serialized.extend(i.to_le_bytes());
|
serialized.extend(i.to_le_bytes());
|
||||||
|
serialized.push(1);
|
||||||
serialized.extend(shares[usize::from(i) - 1].to_repr().as_ref());
|
serialized.extend(shares[usize::from(i) - 1].to_repr().as_ref());
|
||||||
for share in &verification_shares {
|
for share in &verification_shares {
|
||||||
serialized.extend(share.to_bytes().as_ref());
|
serialized.extend(share.to_bytes().as_ref());
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/multiexp"
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["multiexp", "ff", "group"]
|
keywords = ["multiexp", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.79"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ pub(crate) fn prep_bits<G: Group<Scalar: PrimeFieldBits>>(
|
|||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
let p = groupings.len();
|
let p = groupings.len();
|
||||||
let mut bits = pair.0.to_le_bits();
|
let mut bits = pair.0.to_le_bits();
|
||||||
groupings.push(vec![0; (bits.len() + (w_usize - 1)) / w_usize]);
|
groupings.push(vec![0; bits.len().div_ceil(w_usize)]);
|
||||||
|
|
||||||
for (i, mut bit) in bits.iter_mut().enumerate() {
|
for (i, mut bit) in bits.iter_mut().enumerate() {
|
||||||
let mut bit = u8_from_bool(&mut bit);
|
let mut bit = u8_from_bool(&mut bit);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorr"
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["schnorr", "ff", "group"]
|
keywords = ["schnorr", "ff", "group"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.79"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTran
|
|||||||
// Derive a scalar from enough bits of entropy that bias is < 2^128
|
// Derive a scalar from enough bits of entropy that bias is < 2^128
|
||||||
// This can't be const due to its usage of a generic
|
// This can't be const due to its usage of a generic
|
||||||
// Also due to the usize::try_from, yet that could be replaced with an `as`
|
// Also due to the usize::try_from, yet that could be replaced with an `as`
|
||||||
// The + 7 forces it to round up
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let BYTES: usize = usize::try_from(((F::NUM_BITS + 128) + 7) / 8).unwrap();
|
let BYTES: usize = usize::try_from((F::NUM_BITS + 128).div_ceil(8)).unwrap();
|
||||||
|
|
||||||
let mut remaining = BYTES;
|
let mut remaining = BYTES;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorrkel"
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["frost", "multisig", "threshold", "schnorrkel"]
|
keywords = ["frost", "multisig", "threshold", "schnorrkel"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.79"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/transcript"
|
|||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["transcript"]
|
keywords = ["transcript"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.79"
|
rust-version = "1.73"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
Reference in New Issue
Block a user