mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 04:39:24 +00:00
Update the Chaum Pedersen proof to verify the new multi-nonce FROST
Provides further health and reference to https://github.com/serai-dex/serai/issues/14.
This commit is contained in:
@@ -15,13 +15,13 @@ rand_chacha = { version = "0.3", optional = true }
|
|||||||
|
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
|
||||||
ff = "0.11"
|
ff = "0.12"
|
||||||
group = "0.11"
|
group = "0.12"
|
||||||
k256 = { version = "0.10", features = ["arithmetic"] }
|
k256 = { version = "0.11", features = ["arithmetic"] }
|
||||||
|
|
||||||
blake2 = { version = "0.10", optional = true }
|
blake2 = { version = "0.10", optional = true }
|
||||||
transcript = { path = "../../crypto/transcript", optional = true }
|
transcript = { path = "../../crypto/transcript", package = "flexible-transcript", features = ["recommended"], optional = true }
|
||||||
frost = { path = "../../crypto/frost", optional = true }
|
frost = { path = "../../crypto/frost", package = "modular-frost", features = ["secp256k1"], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|||||||
@@ -1,44 +1,26 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
use rand_chacha::ChaCha12Rng;
|
use rand_chacha::ChaCha12Rng;
|
||||||
|
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use group::GroupEncoding;
|
|
||||||
use k256::{Scalar, ProjectivePoint};
|
use k256::{Scalar, ProjectivePoint};
|
||||||
|
|
||||||
use transcript::Transcript as _;
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
use frost::{CurveError, Curve, FrostError, MultisigView, algorithm::Algorithm};
|
use frost::{curve::Secp256k1, FrostError, FrostView, algorithm::Algorithm};
|
||||||
|
|
||||||
use crate::spark::{
|
use crate::spark::{G, GENERATORS_TRANSCRIPT, chaum::{ChaumWitness, ChaumProof}};
|
||||||
G, GENERATORS_TRANSCRIPT,
|
|
||||||
frost::{Transcript, Secp256k1},
|
|
||||||
chaum::{ChaumWitness, ChaumProof}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ChaumMultisig {
|
pub struct ChaumMultisig {
|
||||||
transcript: Transcript,
|
transcript: RecommendedTranscript,
|
||||||
len: usize,
|
len: usize,
|
||||||
witness: ChaumWitness,
|
witness: ChaumWitness,
|
||||||
|
|
||||||
// The following is ugly as hell as it's re-implementing the nonce code FROST is meant to handle
|
|
||||||
// Using FROST's provided SchnorrSignature algorithm multiple times would work, handling nonces
|
|
||||||
// for us, except you need the commitments for the challenge which means you need the binding
|
|
||||||
// factors, which means then you're re-calculating those, and...
|
|
||||||
// The best solution would be for FROST itself to support multi-nonce protocols, if there is
|
|
||||||
// sufficient reason for it to
|
|
||||||
additional_nonces: Vec<(Scalar, Scalar)>,
|
|
||||||
nonces: HashMap<u16, Vec<(ProjectivePoint, ProjectivePoint)>>,
|
|
||||||
sum: Vec<(ProjectivePoint, ProjectivePoint)>,
|
|
||||||
|
|
||||||
challenge: Scalar,
|
challenge: Scalar,
|
||||||
binding: Scalar,
|
|
||||||
proof: Option<ChaumProof>
|
proof: Option<ChaumProof>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChaumMultisig {
|
impl ChaumMultisig {
|
||||||
pub fn new(mut transcript: Transcript, witness: ChaumWitness) -> ChaumMultisig {
|
pub fn new(mut transcript: RecommendedTranscript, witness: ChaumWitness) -> ChaumMultisig {
|
||||||
transcript.domain_separate(b"Chaum");
|
transcript.domain_separate(b"Chaum");
|
||||||
transcript.append_message(b"generators", &*GENERATORS_TRANSCRIPT);
|
transcript.append_message(b"generators", &*GENERATORS_TRANSCRIPT);
|
||||||
transcript.append_message(b"statement", &witness.statement.transcript());
|
transcript.append_message(b"statement", &witness.statement.transcript());
|
||||||
@@ -53,11 +35,6 @@ impl ChaumMultisig {
|
|||||||
len,
|
len,
|
||||||
witness,
|
witness,
|
||||||
|
|
||||||
additional_nonces: Vec::with_capacity(len - 1),
|
|
||||||
nonces: HashMap::new(),
|
|
||||||
sum: vec![(ProjectivePoint::IDENTITY, ProjectivePoint::IDENTITY); len - 1],
|
|
||||||
|
|
||||||
binding: Scalar::zero(),
|
|
||||||
challenge: Scalar::zero(),
|
challenge: Scalar::zero(),
|
||||||
proof: None
|
proof: None
|
||||||
}
|
}
|
||||||
@@ -65,100 +42,57 @@ impl ChaumMultisig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Algorithm<Secp256k1> for ChaumMultisig {
|
impl Algorithm<Secp256k1> for ChaumMultisig {
|
||||||
type Transcript = Transcript;
|
type Transcript = RecommendedTranscript;
|
||||||
type Signature = ChaumProof;
|
type Signature = ChaumProof;
|
||||||
|
|
||||||
fn transcript(&mut self) -> &mut Self::Transcript {
|
fn transcript(&mut self) -> &mut Self::Transcript {
|
||||||
&mut self.transcript
|
&mut self.transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nonces(&self) -> Vec<Vec<ProjectivePoint>> {
|
||||||
|
vec![vec![*G]; self.len]
|
||||||
|
}
|
||||||
|
|
||||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
_: &mut R,
|
||||||
_: &MultisigView<Secp256k1>,
|
_: &FrostView<Secp256k1>
|
||||||
_: &[Scalar; 2],
|
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
// While FROST will provide D_0 and E_0, we need D_i and E_i
|
vec![]
|
||||||
let mut res = Vec::with_capacity((self.len - 1) * 33);
|
|
||||||
for _ in 1 .. self.len {
|
|
||||||
let d = Scalar::random(&mut *rng);
|
|
||||||
let e = Scalar::random(&mut *rng);
|
|
||||||
res.extend(&(*G * d).to_bytes());
|
|
||||||
res.extend(&(*G * e).to_bytes());
|
|
||||||
self.additional_nonces.push((d, e));
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_addendum(
|
fn process_addendum(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &MultisigView<Secp256k1>,
|
_: &FrostView<Secp256k1>,
|
||||||
l: u16,
|
_: u16,
|
||||||
_: &[ProjectivePoint; 2],
|
_: &[u8]
|
||||||
addendum: &[u8],
|
|
||||||
) -> Result<(), FrostError> {
|
) -> Result<(), FrostError> {
|
||||||
let mut nonces = Vec::with_capacity(self.len - 1);
|
|
||||||
for i in 0 .. (self.len - 1) {
|
|
||||||
let p = i * 2;
|
|
||||||
let (D, E) = (|| Ok((
|
|
||||||
Secp256k1::G_from_slice(&addendum[(p * 33) .. ((p + 1) * 33)])?,
|
|
||||||
Secp256k1::G_from_slice(&addendum[((p + 1) * 33) .. ((p + 2) * 33)])?
|
|
||||||
)))().map_err(|_: CurveError| FrostError::InvalidCommitment(l))?;
|
|
||||||
self.transcript.append_message(b"participant", &l.to_be_bytes());
|
|
||||||
self.transcript.append_message(b"commitment_D_additional", &D.to_bytes());
|
|
||||||
self.transcript.append_message(b"commitment_E_additional", &E.to_bytes());
|
|
||||||
self.sum[i].0 += D;
|
|
||||||
self.sum[i].1 += E;
|
|
||||||
nonces.push((D, E));
|
|
||||||
}
|
|
||||||
self.nonces.insert(l, nonces);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_share(
|
fn sign_share(
|
||||||
&mut self,
|
&mut self,
|
||||||
view: &MultisigView<Secp256k1>,
|
view: &FrostView<Secp256k1>,
|
||||||
sum_0: ProjectivePoint,
|
nonce_sums: &[Vec<ProjectivePoint>],
|
||||||
binding: Scalar,
|
nonces: &[Scalar],
|
||||||
nonce_0: Scalar,
|
_: &[u8]
|
||||||
_: &[u8],
|
|
||||||
) -> Scalar {
|
) -> Scalar {
|
||||||
self.binding = binding;
|
|
||||||
|
|
||||||
let (rs, t3, mut commitments) = ChaumProof::r_t_commitments(
|
let (rs, t3, mut commitments) = ChaumProof::r_t_commitments(
|
||||||
&mut ChaCha12Rng::from_seed(self.transcript.rng_seed(b"r_t")),
|
&mut ChaCha12Rng::from_seed(self.transcript.rng_seed(b"r_t")),
|
||||||
&self.witness
|
&self.witness
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut sum = ProjectivePoint::IDENTITY;
|
|
||||||
for i in 0 .. self.len {
|
for i in 0 .. self.len {
|
||||||
let nonce = if i == 0 {
|
commitments.A2[i] += nonce_sums[i][0];
|
||||||
sum_0
|
|
||||||
} else {
|
|
||||||
self.sum[i - 1].0 + (self.sum[i - 1].1 * binding)
|
|
||||||
};
|
|
||||||
commitments.A2[i] += nonce;
|
|
||||||
sum += nonce;
|
|
||||||
}
|
|
||||||
commitments.A1 += sum;
|
|
||||||
|
|
||||||
let mut nonces = Vec::with_capacity(self.len);
|
|
||||||
for i in 0 .. self.len {
|
|
||||||
nonces.push(
|
|
||||||
if i == 0 {
|
|
||||||
nonce_0
|
|
||||||
} else {
|
|
||||||
self.additional_nonces[i - 1].0 + (self.additional_nonces[i - 1].1 * binding)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
commitments.A1 += nonce_sums.iter().map(|sum| sum[0]).sum::<ProjectivePoint>();
|
||||||
|
|
||||||
let (challenge, proof) = ChaumProof::t_prove(
|
let (challenge, proof) = ChaumProof::t_prove(
|
||||||
&self.witness,
|
&self.witness,
|
||||||
&rs,
|
&rs,
|
||||||
t3,
|
t3,
|
||||||
commitments,
|
commitments,
|
||||||
&nonces,
|
nonces,
|
||||||
&view.secret_share()
|
&view.secret_share()
|
||||||
);
|
);
|
||||||
self.challenge = challenge;
|
self.challenge = challenge;
|
||||||
@@ -170,7 +104,7 @@ impl Algorithm<Secp256k1> for ChaumMultisig {
|
|||||||
fn verify(
|
fn verify(
|
||||||
&self,
|
&self,
|
||||||
_: ProjectivePoint,
|
_: ProjectivePoint,
|
||||||
_: ProjectivePoint,
|
_: &[Vec<ProjectivePoint>],
|
||||||
sum: Scalar
|
sum: Scalar
|
||||||
) -> Option<Self::Signature> {
|
) -> Option<Self::Signature> {
|
||||||
let mut proof = self.proof.clone().unwrap();
|
let mut proof = self.proof.clone().unwrap();
|
||||||
@@ -180,23 +114,17 @@ impl Algorithm<Secp256k1> for ChaumMultisig {
|
|||||||
|
|
||||||
fn verify_share(
|
fn verify_share(
|
||||||
&self,
|
&self,
|
||||||
l: u16,
|
_: u16,
|
||||||
verification_share: ProjectivePoint,
|
verification_share: ProjectivePoint,
|
||||||
nonce: ProjectivePoint,
|
nonces: &[Vec<ProjectivePoint>],
|
||||||
share: Scalar,
|
share: Scalar
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut t2 = ProjectivePoint::IDENTITY;
|
let mut t2 = ProjectivePoint::IDENTITY;
|
||||||
let mut accum = self.challenge;
|
let mut accum = self.challenge;
|
||||||
for i in 0 .. self.len {
|
for i in 0 .. self.len {
|
||||||
let nonce = if i == 0 {
|
t2 += nonces[i][0] + (verification_share * accum);
|
||||||
nonce
|
|
||||||
} else {
|
|
||||||
self.nonces[&l][i - 1].0 + (self.nonces[&l][i - 1].1 * self.binding)
|
|
||||||
};
|
|
||||||
t2 += nonce + (verification_share * accum);
|
|
||||||
accum *= self.challenge;
|
accum *= self.challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
(*G * share) == t2
|
(*G * share) == t2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
use core::convert::TryInto;
|
|
||||||
|
|
||||||
use ff::PrimeField;
|
|
||||||
use group::GroupEncoding;
|
|
||||||
|
|
||||||
use sha2::{Digest, Sha256, Sha512};
|
|
||||||
|
|
||||||
use k256::{
|
|
||||||
elliptic_curve::{generic_array::GenericArray, bigint::{ArrayEncoding, U512}, ops::Reduce},
|
|
||||||
Scalar,
|
|
||||||
ProjectivePoint
|
|
||||||
};
|
|
||||||
|
|
||||||
use transcript::DigestTranscript;
|
|
||||||
use frost::{CurveError, Curve};
|
|
||||||
|
|
||||||
use crate::spark::G;
|
|
||||||
|
|
||||||
const CONTEXT: &[u8] = b"FROST-K256-SHA";
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub(crate) struct Secp256k1;
|
|
||||||
impl Curve for Secp256k1 {
|
|
||||||
type F = Scalar;
|
|
||||||
type G = ProjectivePoint;
|
|
||||||
type T = ProjectivePoint;
|
|
||||||
|
|
||||||
fn id() -> String {
|
|
||||||
"secp256k1".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id_len() -> u8 {
|
|
||||||
u8::try_from(Self::id().len()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generator() -> Self::G {
|
|
||||||
*G
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generator_table() -> Self::T {
|
|
||||||
*G
|
|
||||||
}
|
|
||||||
|
|
||||||
fn little_endian() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
// The IETF draft doesn't specify a secp256k1 ciphersuite
|
|
||||||
// This test just uses the simplest ciphersuite which would still be viable to deploy
|
|
||||||
// The comparable P-256 curve uses hash_to_field from the Hash To Curve IETF draft with a context
|
|
||||||
// string and further DST for H1 ("rho") and H3 ("digest"). With lack of hash_to_field, wide
|
|
||||||
// reduction is used
|
|
||||||
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
|
||||||
(&Sha256::digest(&[CONTEXT, b"digest", msg].concat())).to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_binding_factor(binding: &[u8]) -> Self::F {
|
|
||||||
Self::hash_to_F(&[CONTEXT, b"rho", binding].concat())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_to_F(data: &[u8]) -> Self::F {
|
|
||||||
Scalar::from_uint_reduced(U512::from_be_byte_array(Sha512::digest(data)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn F_len() -> usize {
|
|
||||||
32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn G_len() -> usize {
|
|
||||||
33
|
|
||||||
}
|
|
||||||
|
|
||||||
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
|
|
||||||
let bytes: [u8; 32] = slice.try_into()
|
|
||||||
.map_err(|_| CurveError::InvalidLength(32, slice.len()))?;
|
|
||||||
let scalar = Scalar::from_repr(bytes.into());
|
|
||||||
if scalar.is_none().unwrap_u8() == 1 {
|
|
||||||
Err(CurveError::InvalidScalar)?;
|
|
||||||
}
|
|
||||||
Ok(scalar.unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
|
|
||||||
let point = ProjectivePoint::from_bytes(GenericArray::from_slice(slice));
|
|
||||||
if point.is_none().unwrap_u8() == 1 {
|
|
||||||
Err(CurveError::InvalidScalar)?;
|
|
||||||
}
|
|
||||||
Ok(point.unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn F_to_bytes(f: &Self::F) -> Vec<u8> {
|
|
||||||
(&f.to_bytes()).to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
|
|
||||||
(&g.to_bytes()).to_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Transcript = DigestTranscript::<blake2::Blake2b512>;
|
|
||||||
@@ -7,11 +7,12 @@ use k256::{ProjectivePoint, CompressedPoint};
|
|||||||
|
|
||||||
pub mod chaum;
|
pub mod chaum;
|
||||||
|
|
||||||
#[cfg(feature = "frost")]
|
|
||||||
pub(crate) mod frost;
|
|
||||||
|
|
||||||
// Extremely basic hash to curve, which should not be used, yet which offers the needed generators
|
// Extremely basic hash to curve, which should not be used, yet which offers the needed generators
|
||||||
fn generator(letter: u8) -> ProjectivePoint {
|
fn generator(letter: u8) -> ProjectivePoint {
|
||||||
|
if letter == b'G' {
|
||||||
|
return ProjectivePoint::GENERATOR;
|
||||||
|
}
|
||||||
|
|
||||||
let mut point = [2; 33];
|
let mut point = [2; 33];
|
||||||
let mut g = b"Generator ".to_vec();
|
let mut g = b"Generator ".to_vec();
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use ff::Field;
|
|||||||
use k256::Scalar;
|
use k256::Scalar;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use frost::tests::{key_gen, algorithm_machines, sign};
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
|
#[cfg(feature = "multisig")]
|
||||||
|
use frost::{curve::Secp256k1, tests::{key_gen, algorithm_machines, sign}};
|
||||||
|
|
||||||
use crate::spark::{F, G, H, U, chaum::*};
|
use crate::spark::{F, G, H, U, chaum::*};
|
||||||
#[cfg(feature = "multisig")]
|
|
||||||
use crate::spark::frost::{Transcript, Secp256k1};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chaum() {
|
fn chaum() {
|
||||||
@@ -63,7 +63,7 @@ fn chaum_multisig() {
|
|||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
algorithm_machines(
|
algorithm_machines(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
ChaumMultisig::new(Transcript::new(b"Firo Serai Chaum Test".to_vec()), witness),
|
ChaumMultisig::new(RecommendedTranscript::new(b"Firo Serai Chaum Test"), witness),
|
||||||
&keys
|
&keys
|
||||||
),
|
),
|
||||||
&[]
|
&[]
|
||||||
|
|||||||
Reference in New Issue
Block a user