Use a challenge from the FROST transcript as context in the DLEq proofs

This commit is contained in:
Luke Parker 2022-12-13 19:27:09 -05:00
parent ace7506172
commit 783a445a3e
No known key found for this signature in database
3 changed files with 46 additions and 11 deletions

2
Cargo.lock generated
View file

@ -4631,8 +4631,8 @@ dependencies = [
"hkdf", "hkdf",
"minimal-ed448", "minimal-ed448",
"multiexp", "multiexp",
"rand 0.8.5",
"rand_chacha 0.3.1", "rand_chacha 0.3.1",
"rand_core 0.6.4",
"schnorr-signatures", "schnorr-signatures",
"serde_json", "serde_json",
"subtle", "subtle",

View file

@ -27,8 +27,19 @@ use dleq::DLEqProof;
use crate::curve::Curve; use crate::curve::Curve;
fn dleq_transcript<T: Transcript>() -> T { // Every participant proves for their commitments at the start of the protocol
T::new(b"FROST_nonce_dleq") // These proofs are verified sequentially, requiring independent transcripts
// In order to make these transcripts more robust, the FROST transcript (at time of preprocess) is
// challenged in order to create a commitment to it, carried in each independent transcript
// (effectively forking the original transcript)
//
// For FROST, as defined by the IETF, this will do nothing (and this transcript will never even be
// constructed). For higher level protocols, the transcript may have contextual info these proofs
// will then be bound to
fn dleq_transcript<T: Transcript>(context: &[u8]) -> T {
let mut transcript = T::new(b"FROST_commitments");
transcript.append_message(b"context", context);
transcript
} }
// Each nonce is actually a pair of random scalars, notated as d, e under the FROST paper // Each nonce is actually a pair of random scalars, notated as d, e under the FROST paper
@ -67,6 +78,7 @@ impl<C: Curve> NonceCommitments<C> {
rng: &mut R, rng: &mut R,
secret_share: &Zeroizing<C::F>, secret_share: &Zeroizing<C::F>,
generators: &[C::G], generators: &[C::G],
context: &[u8],
) -> (Nonce<C>, NonceCommitments<C>) { ) -> (Nonce<C>, NonceCommitments<C>) {
let nonce = Nonce::<C>([ let nonce = Nonce::<C>([
C::random_nonce(secret_share, &mut *rng), C::random_nonce(secret_share, &mut *rng),
@ -87,8 +99,7 @@ impl<C: Curve> NonceCommitments<C> {
// Uses an independent transcript as each signer must prove this with their commitments, // Uses an independent transcript as each signer must prove this with their commitments,
// yet they're validated while processing everyone's data sequentially, by the global order // yet they're validated while processing everyone's data sequentially, by the global order
// This avoids needing to clone and fork the transcript around // This avoids needing to clone and fork the transcript around
// TODO: At least include a challenge from the existing transcript DLEqProof::prove(&mut *rng, &mut dleq_transcript::<T>(context), generators, nonce)
DLEqProof::prove(&mut *rng, &mut dleq_transcript::<T>(), generators, nonce)
}; };
dleqs = Some([dleq(&nonce.0[0]), dleq(&nonce.0[1])]); dleqs = Some([dleq(&nonce.0[0]), dleq(&nonce.0[1])]);
} }
@ -99,6 +110,7 @@ impl<C: Curve> NonceCommitments<C> {
fn read<R: Read, T: Transcript>( fn read<R: Read, T: Transcript>(
reader: &mut R, reader: &mut R,
generators: &[C::G], generators: &[C::G],
context: &[u8],
) -> io::Result<NonceCommitments<C>> { ) -> io::Result<NonceCommitments<C>> {
let commitments: Vec<GeneratorCommitments<C>> = (0 .. generators.len()) let commitments: Vec<GeneratorCommitments<C>> = (0 .. generators.len())
.map(|_| GeneratorCommitments::read(reader)) .map(|_| GeneratorCommitments::read(reader))
@ -110,7 +122,7 @@ impl<C: Curve> NonceCommitments<C> {
let dleq = DLEqProof::deserialize(reader)?; let dleq = DLEqProof::deserialize(reader)?;
dleq dleq
.verify( .verify(
&mut dleq_transcript::<T>(), &mut dleq_transcript::<T>(context),
generators, generators,
&commitments.iter().map(|commitments| commitments.0[i]).collect::<Vec<_>>(), &commitments.iter().map(|commitments| commitments.0[i]).collect::<Vec<_>>(),
) )
@ -146,12 +158,13 @@ impl<C: Curve> Commitments<C> {
rng: &mut R, rng: &mut R,
secret_share: &Zeroizing<C::F>, secret_share: &Zeroizing<C::F>,
planned_nonces: &[Vec<C::G>], planned_nonces: &[Vec<C::G>],
context: &[u8],
) -> (Vec<Nonce<C>>, Commitments<C>) { ) -> (Vec<Nonce<C>>, Commitments<C>) {
let mut nonces = vec![]; let mut nonces = vec![];
let mut commitments = vec![]; let mut commitments = vec![];
for generators in planned_nonces { for generators in planned_nonces {
let (nonce, these_commitments) = let (nonce, these_commitments) =
NonceCommitments::new::<_, T>(&mut *rng, secret_share, generators); NonceCommitments::new::<_, T>(&mut *rng, secret_share, generators, context);
nonces.push(nonce); nonces.push(nonce);
commitments.push(these_commitments); commitments.push(these_commitments);
} }
@ -183,10 +196,11 @@ impl<C: Curve> Commitments<C> {
pub(crate) fn read<R: Read, T: Transcript>( pub(crate) fn read<R: Read, T: Transcript>(
reader: &mut R, reader: &mut R,
nonces: &[Vec<C::G>], nonces: &[Vec<C::G>],
context: &[u8],
) -> io::Result<Self> { ) -> io::Result<Self> {
Ok(Commitments { Ok(Commitments {
nonces: (0 .. nonces.len()) nonces: (0 .. nonces.len())
.map(|i| NonceCommitments::read::<_, T>(reader, &nonces[i])) .map(|i| NonceCommitments::read::<_, T>(reader, &nonces[i], context))
.collect::<Result<_, _>>()?, .collect::<Result<_, _>>()?,
}) })
} }

View file

@ -115,10 +115,13 @@ impl<C: Curve, A: Algorithm<C>> AlgorithmMachine<C, A> {
let mut params = self.params; let mut params = self.params;
let mut rng = ChaCha20Rng::from_seed(seed.0); let mut rng = ChaCha20Rng::from_seed(seed.0);
// Get a challenge to the existing transcript for use when proving for the commitments
let commitments_challenge = params.algorithm.transcript().challenge(b"commitments");
let (nonces, commitments) = Commitments::new::<_, A::Transcript>( let (nonces, commitments) = Commitments::new::<_, A::Transcript>(
&mut rng, &mut rng,
params.keys.secret_share(), params.keys.secret_share(),
&params.algorithm.nonces(), &params.algorithm.nonces(),
commitments_challenge.as_ref(),
); );
let addendum = params.algorithm.preprocess_addendum(&mut rng, &params.keys); let addendum = params.algorithm.preprocess_addendum(&mut rng, &params.keys);
@ -128,22 +131,35 @@ impl<C: Curve, A: Algorithm<C>> AlgorithmMachine<C, A> {
let mut blame_entropy = [0; 32]; let mut blame_entropy = [0; 32];
rng.fill_bytes(&mut blame_entropy); rng.fill_bytes(&mut blame_entropy);
( (
AlgorithmSignMachine { params, seed, nonces, preprocess: preprocess.clone(), blame_entropy }, AlgorithmSignMachine {
params,
seed,
commitments_challenge,
nonces,
preprocess: preprocess.clone(),
blame_entropy,
},
preprocess, preprocess,
) )
} }
#[cfg(any(test, feature = "tests"))] #[cfg(any(test, feature = "tests"))]
pub(crate) fn unsafe_override_preprocess( pub(crate) fn unsafe_override_preprocess(
self, mut self,
nonces: Vec<Nonce<C>>, nonces: Vec<Nonce<C>>,
preprocess: Preprocess<C, A::Addendum>, preprocess: Preprocess<C, A::Addendum>,
) -> AlgorithmSignMachine<C, A> { ) -> AlgorithmSignMachine<C, A> {
AlgorithmSignMachine { AlgorithmSignMachine {
commitments_challenge: self.params.algorithm.transcript().challenge(b"commitments"),
params: self.params, params: self.params,
seed: Zeroizing::new(CachedPreprocess([0; 32])), seed: Zeroizing::new(CachedPreprocess([0; 32])),
nonces, nonces,
preprocess, preprocess,
// Uses 0s since this is just used to protect against a malicious participant from
// deliberately increasing the amount of time needed to identify them (and is accordingly
// not necessary to function)
blame_entropy: [0; 32], blame_entropy: [0; 32],
} }
} }
@ -221,6 +237,7 @@ pub struct AlgorithmSignMachine<C: Curve, A: Algorithm<C>> {
params: Params<C, A>, params: Params<C, A>,
seed: Zeroizing<CachedPreprocess>, seed: Zeroizing<CachedPreprocess>,
commitments_challenge: <A::Transcript as Transcript>::Challenge,
pub(crate) nonces: Vec<Nonce<C>>, pub(crate) nonces: Vec<Nonce<C>>,
#[zeroize(skip)] #[zeroize(skip)]
pub(crate) preprocess: Preprocess<C, A::Addendum>, pub(crate) preprocess: Preprocess<C, A::Addendum>,
@ -249,7 +266,11 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
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> {
Ok(Preprocess { Ok(Preprocess {
commitments: Commitments::read::<_, A::Transcript>(reader, &self.params.algorithm.nonces())?, commitments: Commitments::read::<_, A::Transcript>(
reader,
&self.params.algorithm.nonces(),
self.commitments_challenge.as_ref(),
)?,
addendum: self.params.algorithm.read_addendum(reader)?, addendum: self.params.algorithm.read_addendum(reader)?,
}) })
} }