Skip to content

Commit

Permalink
Implement protobuf serialization in decaf377-frost
Browse files Browse the repository at this point in the history
  • Loading branch information
cronokirby committed Nov 14, 2023
1 parent 1c72bd9 commit 8cf98cd
Show file tree
Hide file tree
Showing 13 changed files with 1,552 additions and 33 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/crypto/decaf377-frost/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1"
ark-ff = { version = "0.4", default_features = false }
blake2b_simd = "0.5"
decaf377 = "0.5"
decaf377-rdsa = { git = "https://github.com/penumbra-zone/decaf377-rdsa/", rev = "7de0282c887d470e5370d89c67323abd6f41c7a2"}
frost-core = "0.7"
frost-rerandomized = "0.7"
rand_core = "0.6.4"
penumbra-proto = { path = "../../proto/" }
7 changes: 5 additions & 2 deletions crates/crypto/decaf377-frost/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn split<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<(HashMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
// https://github.com/ZcashFoundation/frost/issues/497
let frost_secret = frost_core::SigningKey::deserialize(secret.to_bytes())?;
let frost_secret = frost_core::SigningKey::deserialize(secret.to_bytes().to_vec())?;
frost::keys::split(&frost_secret, max_signers, min_signers, identifiers, rng)
}

Expand All @@ -56,7 +56,10 @@ pub fn split<R: RngCore + CryptoRng>(
pub fn reconstruct(key_packages: &[KeyPackage]) -> Result<SigningKey<SpendAuth>, Error> {
// https://github.com/ZcashFoundation/frost/issues/497
let frost_secret = frost::keys::reconstruct(key_packages)?;
Ok(SigningKey::try_from(frost_secret.serialize()).expect("serialization is valid"))
Ok(SigningKey::try_from(
TryInto::<[u8; 32]>::try_into(frost_secret.serialize()).expect("serialization is valid"),
)
.expect("serialization is valid"))
}

/// Secret and public key material generated by a dealer performing
Expand Down
93 changes: 87 additions & 6 deletions crates/crypto/decaf377-frost/src/keys/dkg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Distributed key generation without a trusted dealer.
use anyhow::anyhow;
use penumbra_proto::crypto::decaf377_frost::v1alpha1 as pb;

// Copied from frost-ed25519 ("MIT or Apache-2.0")
// Copied from frost-ed25519 ("MIT or Apache-2.0") (more or less)

use super::*;

Expand All @@ -18,7 +20,41 @@ pub mod round1 {

/// The package that must be broadcast by each participant to all other participants
/// between the first and second parts of the DKG protocol (round 1).
pub type Package = frost::keys::dkg::round1::Package<E>;
#[derive(Debug, Clone)]
pub struct Package(pub(crate) frost::keys::dkg::round1::Package<E>);

impl From<Package> for pb::DkgRound1Package {
fn from(value: Package) -> Self {
Self {
commitment: Some(pb::VerifiableSecretSharingCommitment {
elements: value
.0
.commitment()
.serialize()
.into_iter()
.map(|x| x.to_vec())
.collect(),
}),
proof_of_knowledge: value.0.proof_of_knowledge().serialize().to_vec(),
}
}
}

impl TryFrom<pb::DkgRound1Package> for Package {
type Error = anyhow::Error;

fn try_from(value: pb::DkgRound1Package) -> Result<Self, Self::Error> {
Ok(Self(frost::keys::dkg::round1::Package::new(
frost::keys::VerifiableSecretSharingCommitment::deserialize(
value
.commitment
.ok_or(anyhow!("DkgRound1Package missing commitment"))?
.elements,
)?,
frost_core::Signature::deserialize(value.proof_of_knowledge)?,
)))
}
}
}

/// DKG Round 2 structures.
Expand All @@ -40,7 +76,33 @@ pub mod round2 {
/// # Security
///
/// The package must be sent on an *confidential* and *authenticated* channel.
pub type Package = frost::keys::dkg::round2::Package<E>;
#[derive(Debug, Clone)]
pub struct Package(pub(crate) frost::keys::dkg::round2::Package<E>);

impl From<Package> for pb::DkgRound2Package {
fn from(value: Package) -> Self {
Self {
signing_share: Some(pb::SigningShare {
scalar: value.0.secret_share().serialize(),
}),
}
}
}

impl TryFrom<pb::DkgRound2Package> for Package {
type Error = anyhow::Error;

fn try_from(value: pb::DkgRound2Package) -> Result<Self, Self::Error> {
Ok(Self(frost::keys::dkg::round2::Package::new(
frost::keys::SigningShare::deserialize(
value
.signing_share
.ok_or(anyhow!("DkgRound2Package missing signing share"))?
.scalar,
)?,
)))
}
}
}

/// Performs the first part of the distributed key generation protocol
Expand All @@ -56,6 +118,7 @@ pub fn part1<R: RngCore + CryptoRng>(
mut rng: R,
) -> Result<(round1::SecretPackage, round1::Package), Error> {
frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng)
.map(|(a, b)| (a, round1::Package(b)))
}

/// Performs the second part of the distributed key generation protocol
Expand All @@ -69,9 +132,19 @@ pub fn part2(
secret_package: round1::SecretPackage,
round1_packages: &HashMap<Identifier, round1::Package>,
) -> Result<(round2::SecretPackage, HashMap<Identifier, round2::Package>), Error> {
frost::keys::dkg::part2(secret_package, round1_packages)
let round1_packages = round1_packages
.iter()
.map(|(a, b)| (*a, b.0.clone()))
.collect();
frost::keys::dkg::part2(secret_package, &round1_packages).map(|(a, b)| {
(
a,
b.into_iter()
.map(|(k, v)| (k, round2::Package(v)))
.collect(),
)
})
}

/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`round2::SecretPackage`],
/// given the received [`round1::Package`]s and [`round2::Package`]s received from
Expand All @@ -86,5 +159,13 @@ pub fn part3(
round1_packages: &HashMap<Identifier, round1::Package>,
round2_packages: &HashMap<Identifier, round2::Package>,
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages)
let round1_packages = round1_packages
.iter()
.map(|(a, b)| (*a, b.0.clone()))
.collect();
let round2_packages = round2_packages
.iter()
.map(|(a, b)| (*a, b.0.clone()))
.collect();
frost::keys::dkg::part3(round2_secret_package, &round1_packages, &round2_packages)
}
90 changes: 77 additions & 13 deletions crates/crypto/decaf377-frost/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
//! This implementation only supports producing `SpendAuth` signatures, which
//! use the conventional `decaf377` basepoint.
use anyhow::anyhow;
use frost_core::frost;
use penumbra_proto::crypto::decaf377_frost::v1alpha1 as pb;
use std::collections::HashMap;

/// A FROST-related error.
Expand Down Expand Up @@ -43,13 +45,42 @@ pub mod round1 {
///
/// This step can be batched if desired by the implementation. Each
/// SigningCommitment can be used for exactly *one* signature.
pub type SigningCommitments = frost::round1::SigningCommitments<E>;
#[derive(Debug, Clone)]
pub struct SigningCommitments(frost::round1::SigningCommitments<E>);

impl From<SigningCommitments> for pb::SigningCommitments {
fn from(value: SigningCommitments) -> Self {
Self {
hiding: Some(pb::NonceCommitment {
element: value.0.hiding().serialize(),
}),
binding: Some(pb::NonceCommitment {
element: value.0.binding().serialize(),
}),
}
}
}

/*
// TODO: doesn't seem like this is used directly?
/// A commitment to a signing nonce share.
pub type NonceCommitment = frost::round1::NonceCommitment<E>;
*/
impl TryFrom<pb::SigningCommitments> for SigningCommitments {
type Error = anyhow::Error;

fn try_from(value: pb::SigningCommitments) -> Result<Self, Self::Error> {
Ok(Self(frost::round1::SigningCommitments::new(
frost::round1::NonceCommitment::deserialize(
value
.hiding
.ok_or(anyhow!("SigningCommitments missing hiding"))?
.element,
)?,
frost::round1::NonceCommitment::deserialize(
value
.binding
.ok_or(anyhow!("SigningCommitments missing binding"))?
.element,
)?,
)))
}
}

/// Performed once by each participant selected for the signing operation.
///
Expand All @@ -59,7 +90,8 @@ pub mod round1 {
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<E, RNG>(secret, rng)
let (a, b) = frost::round1::commit::<E, RNG>(secret, rng);
(a, SigningCommitments(b))
}
}

Expand All @@ -75,7 +107,26 @@ pub mod round2 {

/// A FROST participant's signature share, which the Coordinator will
/// aggregate with all other signer's shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<E>;
#[derive(Debug, Clone)]
pub struct SignatureShare(pub(crate) frost::round2::SignatureShare<E>);

impl From<SignatureShare> for pb::SignatureShare {
fn from(value: SignatureShare) -> Self {
pb::SignatureShare {
scalar: value.0.serialize(),
}
}
}

impl TryFrom<pb::SignatureShare> for SignatureShare {
type Error = anyhow::Error;

fn try_from(value: pb::SignatureShare) -> Result<Self, Self::Error> {
Ok(Self(frost::round2::SignatureShare::deserialize(
value.scalar,
)?))
}
}

/// Performed once by each participant selected for the signing operation.
///
Expand All @@ -90,7 +141,7 @@ pub mod round2 {
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
) -> Result<SignatureShare, Error> {
frost::round2::sign(signing_package, signer_nonces, key_package)
frost::round2::sign(signing_package, signer_nonces, key_package).map(SignatureShare)
}

/// Like [`sign`], but for producing signatures with a randomized verification key.
Expand All @@ -106,6 +157,7 @@ pub mod round2 {
key_package,
Randomizer::from_scalar(randomizer),
)
.map(SignatureShare)
}
}

Expand All @@ -131,8 +183,14 @@ pub fn aggregate(
signature_shares: &HashMap<Identifier, round2::SignatureShare>,
pubkeys: &keys::PublicKeyPackage,
) -> Result<Signature<SpendAuth>, Error> {
let frost_sig = frost::aggregate(signing_package, signature_shares, pubkeys)?;
Ok(frost_sig.serialize())
let signature_shares = signature_shares
.iter()
.map(|(a, b)| (*a, b.0.clone()))
.collect();
let frost_sig = frost::aggregate(signing_package, &signature_shares, pubkeys)?;
Ok(TryInto::<[u8; 64]>::try_into(frost_sig.serialize())
.expect("serialization is valid")
.into())
}

/// Like [`aggregate`], but for generating signatures with a randomized
Expand All @@ -143,14 +201,20 @@ pub fn aggregate_randomized(
pubkeys: &keys::PublicKeyPackage,
randomizer: decaf377::Fr,
) -> Result<Signature<SpendAuth>, Error> {
let signature_shares = signature_shares
.iter()
.map(|(a, b)| (*a, b.0.clone()))
.collect();
let frost_sig = frost_rerandomized::aggregate(
signing_package,
signature_shares,
&signature_shares,
pubkeys,
&frost_rerandomized::RandomizedParams::from_randomizer(
pubkeys.group_public(),
frost_rerandomized::Randomizer::from_scalar(randomizer),
),
)?;
Ok(frost_sig.serialize())
Ok(TryInto::<[u8; 64]>::try_into(frost_sig.serialize())
.expect("serialization is valid")
.into())
}
Loading

0 comments on commit 8cf98cd

Please sign in to comment.