Files
serai/crypto/frost/src/tests/nonces.rs
Luke Parker a25e6330bd Remove DLEq proofs from CLSAG multisig
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).
2024-04-21 23:01:32 -04:00

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, &[]);
}