mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
1) Removes the key image DLEq on the Monero side of things, as the produced signature share serves as a DLEq for it. 2) Removes the nonce DLEqs from modular-frost as they're unnecessary for monero-serai. Updates documentation accordingly. Without the proof the nonces are internally consistent, the produced signatures from modular-frost can be argued as a batch-verifiable CP93 DLEq (R0, R1, s), or as a GSP for the CP93 DLEq statement (which naturally produces (R0, R1, s)). The lack of proving the nonces consistent does make the process weaker, yet it's also unnecessary for the class of protocols this is intended to service. To provide DLEqs for the nonces would be to provide PoKs for the nonce commitments (in the traditional Schnorr case).
158 lines
4.7 KiB
Rust
158 lines
4.7 KiB
Rust
use std::io::{self, Read};
|
|
|
|
use zeroize::Zeroizing;
|
|
|
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
|
use rand_chacha::ChaCha20Rng;
|
|
|
|
use transcript::{Transcript, RecommendedTranscript};
|
|
|
|
use ciphersuite::group::{ff::Field, Group, GroupEncoding};
|
|
|
|
pub use dkg::tests::{key_gen, recover_key};
|
|
|
|
use crate::{
|
|
Curve, Participant, ThresholdView, ThresholdKeys, FrostError,
|
|
algorithm::Algorithm,
|
|
tests::{algorithm_machines, sign},
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
struct MultiNonce<C: Curve> {
|
|
transcript: RecommendedTranscript,
|
|
nonces: Option<Vec<Vec<C::G>>>,
|
|
}
|
|
|
|
impl<C: Curve> MultiNonce<C> {
|
|
fn new() -> MultiNonce<C> {
|
|
MultiNonce {
|
|
transcript: RecommendedTranscript::new(b"FROST MultiNonce Algorithm Test"),
|
|
nonces: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn nonces<C: Curve>() -> Vec<Vec<C::G>> {
|
|
vec![
|
|
vec![C::generator(), C::generator().double()],
|
|
vec![C::generator(), C::generator() * C::F::from(3), C::generator() * C::F::from(4)],
|
|
]
|
|
}
|
|
|
|
fn verify_nonces<C: Curve>(nonces: &[Vec<C::G>]) {
|
|
assert_eq!(nonces.len(), 2);
|
|
|
|
// Each nonce should be a series of commitments, over some generators, which share a discrete log
|
|
// Since they share a discrete log, their only distinction should be the generator
|
|
// Above, the generators were created with a known relationship
|
|
// Accordingly, we can check here that relationship holds to make sure these commitments are well
|
|
// formed
|
|
assert_eq!(nonces[0].len(), 2);
|
|
assert_eq!(nonces[0][0].double(), nonces[0][1]);
|
|
|
|
assert_eq!(nonces[1].len(), 3);
|
|
assert_eq!(nonces[1][0] * C::F::from(3), nonces[1][1]);
|
|
assert_eq!(nonces[1][0] * C::F::from(4), nonces[1][2]);
|
|
|
|
assert!(nonces[0][0] != nonces[1][0]);
|
|
}
|
|
|
|
impl<C: Curve> Algorithm<C> for MultiNonce<C> {
|
|
type Transcript = RecommendedTranscript;
|
|
type Addendum = ();
|
|
type Signature = ();
|
|
|
|
fn transcript(&mut self) -> &mut Self::Transcript {
|
|
&mut self.transcript
|
|
}
|
|
|
|
fn nonces(&self) -> Vec<Vec<C::G>> {
|
|
nonces::<C>()
|
|
}
|
|
|
|
fn preprocess_addendum<R: RngCore + CryptoRng>(&mut self, _: &mut R, _: &ThresholdKeys<C>) {}
|
|
|
|
fn read_addendum<R: Read>(&self, _: &mut R) -> io::Result<Self::Addendum> {
|
|
Ok(())
|
|
}
|
|
|
|
fn process_addendum(
|
|
&mut self,
|
|
_: &ThresholdView<C>,
|
|
_: Participant,
|
|
(): (),
|
|
) -> Result<(), FrostError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn sign_share(
|
|
&mut self,
|
|
_: &ThresholdView<C>,
|
|
nonce_sums: &[Vec<C::G>],
|
|
nonces: Vec<Zeroizing<C::F>>,
|
|
_: &[u8],
|
|
) -> C::F {
|
|
// Verify the nonce sums are as expected
|
|
verify_nonces::<C>(nonce_sums);
|
|
|
|
// Verify we actually have two nonces and that they're distinct
|
|
assert_eq!(nonces.len(), 2);
|
|
assert!(nonces[0] != nonces[1]);
|
|
|
|
// Save the nonce sums for later so we can check they're consistent with the call to verify
|
|
assert!(self.nonces.is_none());
|
|
self.nonces = Some(nonce_sums.to_vec());
|
|
|
|
// Sum the nonces so we can later check they actually have a relationship to nonce_sums
|
|
let mut res = C::F::ZERO;
|
|
|
|
// Weight each nonce
|
|
// This is probably overkill, since their unweighted forms would practically still require
|
|
// some level of crafting to pass a naive sum via malleability, yet this makes it more robust
|
|
for nonce in nonce_sums {
|
|
self.transcript.domain_separate(b"nonce");
|
|
for commitment in nonce {
|
|
self.transcript.append_message(b"commitment", commitment.to_bytes());
|
|
}
|
|
}
|
|
let mut rng = ChaCha20Rng::from_seed(self.transcript.clone().rng_seed(b"weight"));
|
|
|
|
for nonce in nonces {
|
|
res += *nonce * C::F::random(&mut rng);
|
|
}
|
|
res
|
|
}
|
|
|
|
#[must_use]
|
|
fn verify(&self, _: C::G, nonces: &[Vec<C::G>], sum: C::F) -> Option<Self::Signature> {
|
|
verify_nonces::<C>(nonces);
|
|
assert_eq!(&self.nonces.clone().unwrap(), nonces);
|
|
|
|
// Make sure the nonce sums actually relate to the nonces
|
|
let mut res = C::G::identity();
|
|
let mut rng = ChaCha20Rng::from_seed(self.transcript.clone().rng_seed(b"weight"));
|
|
for nonce in nonces {
|
|
res += nonce[0] * C::F::random(&mut rng);
|
|
}
|
|
assert_eq!(res, C::generator() * sum);
|
|
|
|
Some(())
|
|
}
|
|
|
|
fn verify_share(&self, _: C::G, _: &[Vec<C::G>], _: C::F) -> Result<Vec<(C::F, C::G)>, ()> {
|
|
panic!("share verification triggered");
|
|
}
|
|
}
|
|
|
|
/// Test a multi-nonce, multi-generator algorithm.
|
|
// Specifically verifies this library can:
|
|
// 1) Generate multiple nonces
|
|
// 2) Provide the group nonces (nonce_sums) across multiple generators, still with the same
|
|
// discrete log
|
|
// 3) Provide algorithms with nonces which match the group nonces
|
|
pub fn test_multi_nonce<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
|
let keys = key_gen::<R, C>(&mut *rng);
|
|
let machines = algorithm_machines(&mut *rng, &MultiNonce::<C>::new(), &keys);
|
|
sign(&mut *rng, &MultiNonce::<C>::new(), keys.clone(), machines, &[]);
|
|
}
|