From 8cf98cd83811d38158992dbb91595ff9c1105aa0 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Tue, 14 Nov 2023 02:11:44 -0800 Subject: [PATCH] Implement protobuf serialization in decaf377-frost --- Cargo.lock | 2 + crates/crypto/decaf377-frost/Cargo.toml | 2 + crates/crypto/decaf377-frost/src/keys.rs | 7 +- crates/crypto/decaf377-frost/src/keys/dkg.rs | 93 ++- crates/crypto/decaf377-frost/src/lib.rs | 90 ++- crates/crypto/decaf377-frost/src/traits.rs | 27 +- ...penumbra.crypto.decaf377_frost.v1alpha1.rs | 46 ++ ...ra.crypto.decaf377_frost.v1alpha1.serde.rs | 688 ++++++++++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 353361 -> 354851 bytes crates/proto/src/lib.rs | 7 + .../v1alpha1/decaf377_frost.pb.go | 588 +++++++++++++++ .../v1alpha1/decaf377_frost.proto | 34 + tools/proto-compiler/src/main.rs | 1 + 13 files changed, 1552 insertions(+), 33 deletions(-) create mode 100644 crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.rs create mode 100644 crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.serde.rs create mode 100644 proto/go/gen/penumbra/crypto/decaf377_frost/v1alpha1/decaf377_frost.pb.go create mode 100644 proto/penumbra/penumbra/crypto/decaf377_frost/v1alpha1/decaf377_frost.proto diff --git a/Cargo.lock b/Cargo.lock index 05f193e00e..326c592cfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2039,12 +2039,14 @@ dependencies = [ name = "decaf377-frost" version = "0.63.1" dependencies = [ + "anyhow", "ark-ff", "blake2b_simd 0.5.11", "decaf377 0.5.0", "decaf377-rdsa 0.8.0", "frost-core 0.7.0", "frost-rerandomized 0.7.0", + "penumbra-proto", "rand_core 0.6.4", ] diff --git a/crates/crypto/decaf377-frost/Cargo.toml b/crates/crypto/decaf377-frost/Cargo.toml index 04c2338b88..013eaccc72 100644 --- a/crates/crypto/decaf377-frost/Cargo.toml +++ b/crates/crypto/decaf377-frost/Cargo.toml @@ -6,6 +6,7 @@ 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" @@ -13,3 +14,4 @@ decaf377-rdsa = { git = "https://github.com/penumbra-zone/decaf377-rdsa/", rev = frost-core = "0.7" frost-rerandomized = "0.7" rand_core = "0.6.4" +penumbra-proto = { path = "../../proto/" } diff --git a/crates/crypto/decaf377-frost/src/keys.rs b/crates/crypto/decaf377-frost/src/keys.rs index fe810093d2..bef607c062 100644 --- a/crates/crypto/decaf377-frost/src/keys.rs +++ b/crates/crypto/decaf377-frost/src/keys.rs @@ -38,7 +38,7 @@ pub fn split( rng: &mut R, ) -> Result<(HashMap, 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) } @@ -56,7 +56,10 @@ pub fn split( pub fn reconstruct(key_packages: &[KeyPackage]) -> Result, 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 diff --git a/crates/crypto/decaf377-frost/src/keys/dkg.rs b/crates/crypto/decaf377-frost/src/keys/dkg.rs index f54178ea79..b8e87d5f78 100644 --- a/crates/crypto/decaf377-frost/src/keys/dkg.rs +++ b/crates/crypto/decaf377-frost/src/keys/dkg.rs @@ -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::*; @@ -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; + #[derive(Debug, Clone)] + pub struct Package(pub(crate) frost::keys::dkg::round1::Package); + + impl From 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 for Package { + type Error = anyhow::Error; + + fn try_from(value: pb::DkgRound1Package) -> Result { + 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. @@ -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; + #[derive(Debug, Clone)] + pub struct Package(pub(crate) frost::keys::dkg::round2::Package); + + impl From for pb::DkgRound2Package { + fn from(value: Package) -> Self { + Self { + signing_share: Some(pb::SigningShare { + scalar: value.0.secret_share().serialize(), + }), + } + } + } + + impl TryFrom for Package { + type Error = anyhow::Error; + + fn try_from(value: pb::DkgRound2Package) -> Result { + 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 @@ -56,6 +118,7 @@ pub fn part1( 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 @@ -69,9 +132,19 @@ pub fn part2( secret_package: round1::SecretPackage, round1_packages: &HashMap, ) -> Result<(round2::SecretPackage, HashMap), 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 @@ -86,5 +159,13 @@ pub fn part3( round1_packages: &HashMap, round2_packages: &HashMap, ) -> 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) } diff --git a/crates/crypto/decaf377-frost/src/lib.rs b/crates/crypto/decaf377-frost/src/lib.rs index 30e973dd4c..68fd529ede 100644 --- a/crates/crypto/decaf377-frost/src/lib.rs +++ b/crates/crypto/decaf377-frost/src/lib.rs @@ -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. @@ -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; + #[derive(Debug, Clone)] + pub struct SigningCommitments(frost::round1::SigningCommitments); + + impl From 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; - */ + impl TryFrom for SigningCommitments { + type Error = anyhow::Error; + + fn try_from(value: pb::SigningCommitments) -> Result { + 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. /// @@ -59,7 +90,8 @@ pub mod round1 { where RNG: CryptoRng + RngCore, { - frost::round1::commit::(secret, rng) + let (a, b) = frost::round1::commit::(secret, rng); + (a, SigningCommitments(b)) } } @@ -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; + #[derive(Debug, Clone)] + pub struct SignatureShare(pub(crate) frost::round2::SignatureShare); + + impl From for pb::SignatureShare { + fn from(value: SignatureShare) -> Self { + pb::SignatureShare { + scalar: value.0.serialize(), + } + } + } + + impl TryFrom for SignatureShare { + type Error = anyhow::Error; + + fn try_from(value: pb::SignatureShare) -> Result { + Ok(Self(frost::round2::SignatureShare::deserialize( + value.scalar, + )?)) + } + } /// Performed once by each participant selected for the signing operation. /// @@ -90,7 +141,7 @@ pub mod round2 { signer_nonces: &round1::SigningNonces, key_package: &keys::KeyPackage, ) -> Result { - 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. @@ -106,6 +157,7 @@ pub mod round2 { key_package, Randomizer::from_scalar(randomizer), ) + .map(SignatureShare) } } @@ -131,8 +183,14 @@ pub fn aggregate( signature_shares: &HashMap, pubkeys: &keys::PublicKeyPackage, ) -> Result, 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 @@ -143,14 +201,20 @@ pub fn aggregate_randomized( pubkeys: &keys::PublicKeyPackage, randomizer: decaf377::Fr, ) -> Result, 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()) } diff --git a/crates/crypto/decaf377-frost/src/traits.rs b/crates/crypto/decaf377-frost/src/traits.rs index 71d2c9780a..b8092dd711 100644 --- a/crates/crypto/decaf377-frost/src/traits.rs +++ b/crates/crypto/decaf377-frost/src/traits.rs @@ -1,6 +1,5 @@ use ark_ff::{Field as _, One, UniformRand, Zero}; use decaf377::{Element, FieldExt, Fr}; -use decaf377_rdsa::SpendAuth; pub use frost_core::{frost, Ciphersuite, Field, FieldError, Group, GroupError}; use rand_core; @@ -12,7 +11,7 @@ pub struct Decaf377ScalarField; impl Field for Decaf377ScalarField { type Scalar = Fr; - type Serialization = [u8; 32]; + type Serialization = Vec; fn zero() -> Self::Scalar { Fr::zero() @@ -31,15 +30,18 @@ impl Field for Decaf377ScalarField { } fn serialize(scalar: &Self::Scalar) -> Self::Serialization { - scalar.to_bytes() + scalar.to_bytes().to_vec() } fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization { - scalar.to_bytes() + Self::serialize(scalar) } fn deserialize(buf: &Self::Serialization) -> Result { - Fr::from_bytes(*buf).map_err(|_| FieldError::MalformedScalar) + Fr::from_bytes( + TryInto::<[u8; 32]>::try_into(buf.clone()).map_err(|_| FieldError::MalformedScalar)?, + ) + .map_err(|_| FieldError::MalformedScalar) } } @@ -51,7 +53,7 @@ impl Group for Decaf377Group { type Element = Element; - type Serialization = [u8; 32]; + type Serialization = Vec; fn cofactor() -> ::Scalar { Fr::one() @@ -66,13 +68,15 @@ impl Group for Decaf377Group { } fn serialize(element: &Self::Element) -> Self::Serialization { - element.vartime_compress().0 + element.vartime_compress().0.to_vec() } fn deserialize(buf: &Self::Serialization) -> Result { - decaf377::Encoding(*buf) - .vartime_decompress() - .map_err(|_| GroupError::MalformedElement) + decaf377::Encoding( + TryInto::<[u8; 32]>::try_into(buf.clone()).map_err(|_| GroupError::MalformedElement)?, + ) + .vartime_decompress() + .map_err(|_| GroupError::MalformedElement) } } @@ -87,7 +91,7 @@ impl Ciphersuite for Decaf377Rdsa { type HashOutput = [u8; 32]; - type SignatureSerialization = crate::Signature; + type SignatureSerialization = Vec; fn H1(m: &[u8]) -> <::Field as Field>::Scalar { Hasher::default().update(b"rho").update(m).finalize_scalar() @@ -119,5 +123,4 @@ impl Ciphersuite for Decaf377Rdsa { fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { Some(Hasher::default().update(b"id").update(m).finalize_scalar()) } - } diff --git a/crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.rs b/crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.rs new file mode 100644 index 0000000000..9cafe4d71a --- /dev/null +++ b/crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.rs @@ -0,0 +1,46 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifiableSecretSharingCommitment { + #[prost(bytes = "vec", repeated, tag = "1")] + pub elements: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DkgRound1Package { + #[prost(message, optional, tag = "1")] + pub commitment: ::core::option::Option, + #[prost(bytes = "vec", tag = "2")] + pub proof_of_knowledge: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SigningShare { + #[prost(bytes = "vec", tag = "1")] + pub scalar: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DkgRound2Package { + #[prost(message, optional, tag = "1")] + pub signing_share: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NonceCommitment { + #[prost(bytes = "vec", tag = "1")] + pub element: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SigningCommitments { + #[prost(message, optional, tag = "1")] + pub hiding: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub binding: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SignatureShare { + #[prost(bytes = "vec", tag = "1")] + pub scalar: ::prost::alloc::vec::Vec, +} diff --git a/crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.serde.rs b/crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.serde.rs new file mode 100644 index 0000000000..56e610a8ad --- /dev/null +++ b/crates/proto/src/gen/penumbra.crypto.decaf377_frost.v1alpha1.serde.rs @@ -0,0 +1,688 @@ +impl serde::Serialize for DkgRound1Package { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.commitment.is_some() { + len += 1; + } + if !self.proof_of_knowledge.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.DKGRound1Package", len)?; + if let Some(v) = self.commitment.as_ref() { + struct_ser.serialize_field("commitment", v)?; + } + if !self.proof_of_knowledge.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("proofOfKnowledge", pbjson::private::base64::encode(&self.proof_of_knowledge).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DkgRound1Package { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "commitment", + "proof_of_knowledge", + "proofOfKnowledge", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Commitment, + ProofOfKnowledge, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "commitment" => Ok(GeneratedField::Commitment), + "proofOfKnowledge" | "proof_of_knowledge" => Ok(GeneratedField::ProofOfKnowledge), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DkgRound1Package; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.DKGRound1Package") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut commitment__ = None; + let mut proof_of_knowledge__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Commitment => { + if commitment__.is_some() { + return Err(serde::de::Error::duplicate_field("commitment")); + } + commitment__ = map_.next_value()?; + } + GeneratedField::ProofOfKnowledge => { + if proof_of_knowledge__.is_some() { + return Err(serde::de::Error::duplicate_field("proofOfKnowledge")); + } + proof_of_knowledge__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(DkgRound1Package { + commitment: commitment__, + proof_of_knowledge: proof_of_knowledge__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.DKGRound1Package", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DkgRound2Package { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.signing_share.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.DKGRound2Package", len)?; + if let Some(v) = self.signing_share.as_ref() { + struct_ser.serialize_field("signingShare", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DkgRound2Package { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "signing_share", + "signingShare", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SigningShare, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "signingShare" | "signing_share" => Ok(GeneratedField::SigningShare), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DkgRound2Package; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.DKGRound2Package") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut signing_share__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SigningShare => { + if signing_share__.is_some() { + return Err(serde::de::Error::duplicate_field("signingShare")); + } + signing_share__ = map_.next_value()?; + } + } + } + Ok(DkgRound2Package { + signing_share: signing_share__, + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.DKGRound2Package", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for NonceCommitment { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.element.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.NonceCommitment", len)?; + if !self.element.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("element", pbjson::private::base64::encode(&self.element).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for NonceCommitment { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "element", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Element, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "element" => Ok(GeneratedField::Element), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = NonceCommitment; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.NonceCommitment") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut element__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Element => { + if element__.is_some() { + return Err(serde::de::Error::duplicate_field("element")); + } + element__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(NonceCommitment { + element: element__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.NonceCommitment", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SignatureShare { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.scalar.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.SignatureShare", len)?; + if !self.scalar.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("scalar", pbjson::private::base64::encode(&self.scalar).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SignatureShare { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "scalar", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Scalar, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "scalar" => Ok(GeneratedField::Scalar), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SignatureShare; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.SignatureShare") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut scalar__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Scalar => { + if scalar__.is_some() { + return Err(serde::de::Error::duplicate_field("scalar")); + } + scalar__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(SignatureShare { + scalar: scalar__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.SignatureShare", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SigningCommitments { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.hiding.is_some() { + len += 1; + } + if self.binding.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.SigningCommitments", len)?; + if let Some(v) = self.hiding.as_ref() { + struct_ser.serialize_field("hiding", v)?; + } + if let Some(v) = self.binding.as_ref() { + struct_ser.serialize_field("binding", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SigningCommitments { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "hiding", + "binding", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Hiding, + Binding, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "hiding" => Ok(GeneratedField::Hiding), + "binding" => Ok(GeneratedField::Binding), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SigningCommitments; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.SigningCommitments") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut hiding__ = None; + let mut binding__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Hiding => { + if hiding__.is_some() { + return Err(serde::de::Error::duplicate_field("hiding")); + } + hiding__ = map_.next_value()?; + } + GeneratedField::Binding => { + if binding__.is_some() { + return Err(serde::de::Error::duplicate_field("binding")); + } + binding__ = map_.next_value()?; + } + } + } + Ok(SigningCommitments { + hiding: hiding__, + binding: binding__, + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.SigningCommitments", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SigningShare { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.scalar.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.SigningShare", len)?; + if !self.scalar.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("scalar", pbjson::private::base64::encode(&self.scalar).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SigningShare { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "scalar", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Scalar, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "scalar" => Ok(GeneratedField::Scalar), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SigningShare; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.SigningShare") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut scalar__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Scalar => { + if scalar__.is_some() { + return Err(serde::de::Error::duplicate_field("scalar")); + } + scalar__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(SigningShare { + scalar: scalar__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.SigningShare", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for VerifiableSecretSharingCommitment { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.elements.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.VerifiableSecretSharingCommitment", len)?; + if !self.elements.is_empty() { + struct_ser.serialize_field("elements", &self.elements.iter().map(pbjson::private::base64::encode).collect::>())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for VerifiableSecretSharingCommitment { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "elements", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Elements, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "elements" => Ok(GeneratedField::Elements), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = VerifiableSecretSharingCommitment; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.crypto.decaf377_frost.v1alpha1.VerifiableSecretSharingCommitment") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut elements__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Elements => { + if elements__.is_some() { + return Err(serde::de::Error::duplicate_field("elements")); + } + elements__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + } + } + Ok(VerifiableSecretSharingCommitment { + elements: elements__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.crypto.decaf377_frost.v1alpha1.VerifiableSecretSharingCommitment", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 4d483e94c2a5768873ac21cb9ad2d10ef96edfef..6212b548ba39384b60d23bb914aa4af7dadff740 100644 GIT binary patch delta 1364 zcmaKq&1(}u7{+&JX4CB?&Bvrk^VKAcRx6dXX_0ymJSYe)Xi33CFJYTZ(%59TCaE6d zp^%e^UiJ_0DtHkDZ$b}VLqbZN9yCB)SF}T7=6J% zPsy(^ec-?UYi1YQ18+E5c~x_Ihx<-#rNx_0dwpZ0A-`7-2j0kIw|!@Vb7Rq2)t!}x zbm2K4blY8Lug72b{={Ces|R8?ug~6&3jzD4l`yMFGcEST=`_u_~Wf7 zb??Y+)wZ4Hfz#pa1*Oe1IY`W<@Y-hekh@2HzdgaJ#Y-WoS0wP?eZc84yGt1i;I$j@ z9Jt==9&dGc27zg$9+#D8?JfPia*IZGx*Zn=5OZ*rrlDce={W-#BT`pXanEC~>Daq; zW*BH2Lm?It|DKCt{=o#tFK@1T@sxT*>ExM}Wg2_xxlMjyg&8`fR;Y4ltb8*mQ(ou& zL$*yr``s3dq1Jc5Dz9(#^5*_Krmi%sPN(*|u8{jL8z?FB?_Mv}g31z&iagHfXuz-C z;pTUXh8Qu7vhRF0XUD6QQbY_!h!Ha39TUT3nAmq{n8G(?Fl?bBjZjMf5kxgvK;gJ1 zkXebWCJa=Vm*yrTwh<1@P3W@ij5IgFe2k~jy3_!L7|;z(&^djxstNq2YimjeC5lR_ zWI_yzrZkC~ozny|I$xAA5F;~2EQo=iYsQ|X;V}$}#E?;xlo|l=iz+`L=#1J?T?4;p zf=NHjNWw@4ei*tEYAh`57+R1L0GNuQzyJsuS*)OIcnnx7OFzO$%9soM2(+c_IXNy7 zerYr-H2@GVt>OVeW7>}C8pKPpq_ndb$r^>g&VsHiEJa+YCG2u&UP`R7%$Gxn0Ht4! zWi)|bE?-bOFv+W5!9pgCl|Pe-Yt6IZ@}UN@yt;h&*NR3th!KLWqMep@ArYg5mV>PW d!B{0V77%on)YgH(uasbulT-Zl penumbra.crypto.decaf377_frost.v1alpha1.VerifiableSecretSharingCommitment + 2, // 1: penumbra.crypto.decaf377_frost.v1alpha1.DKGRound2Package.signing_share:type_name -> penumbra.crypto.decaf377_frost.v1alpha1.SigningShare + 4, // 2: penumbra.crypto.decaf377_frost.v1alpha1.SigningCommitments.hiding:type_name -> penumbra.crypto.decaf377_frost.v1alpha1.NonceCommitment + 4, // 3: penumbra.crypto.decaf377_frost.v1alpha1.SigningCommitments.binding:type_name -> penumbra.crypto.decaf377_frost.v1alpha1.NonceCommitment + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_init() } +func file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_init() { + if File_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifiableSecretSharingCommitment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DKGRound1Package); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningShare); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DKGRound2Package); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NonceCommitment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SigningCommitments); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignatureShare); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_goTypes, + DependencyIndexes: file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_depIdxs, + MessageInfos: file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_msgTypes, + }.Build() + File_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto = out.File + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_rawDesc = nil + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_goTypes = nil + file_penumbra_crypto_decaf377_frost_v1alpha1_decaf377_frost_proto_depIdxs = nil +} diff --git a/proto/penumbra/penumbra/crypto/decaf377_frost/v1alpha1/decaf377_frost.proto b/proto/penumbra/penumbra/crypto/decaf377_frost/v1alpha1/decaf377_frost.proto new file mode 100644 index 0000000000..888f034fa7 --- /dev/null +++ b/proto/penumbra/penumbra/crypto/decaf377_frost/v1alpha1/decaf377_frost.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package penumbra.crypto.decaf377_frost.v1alpha1; + +message VerifiableSecretSharingCommitment { + repeated bytes elements = 1; +} + +message DKGRound1Package { + VerifiableSecretSharingCommitment commitment = 1; + bytes proof_of_knowledge = 2; +} + +message SigningShare { + bytes scalar = 1; +} + +message DKGRound2Package { + SigningShare signing_share = 1; +} + +message NonceCommitment { + bytes element = 1; +} + +message SigningCommitments { + NonceCommitment hiding = 1; + NonceCommitment binding = 2; +} + +message SignatureShare { + bytes scalar = 1; +} + diff --git a/tools/proto-compiler/src/main.rs b/tools/proto-compiler/src/main.rs index efc6cb47cd..de8c785cde 100644 --- a/tools/proto-compiler/src/main.rs +++ b/tools/proto-compiler/src/main.rs @@ -102,6 +102,7 @@ fn main() -> anyhow::Result<()> { "../../proto/penumbra/penumbra/core/num/v1alpha1/num.proto", "../../proto/penumbra/penumbra/core/transaction/v1alpha1/transaction.proto", "../../proto/penumbra/penumbra/crypto/decaf377_fmd/v1alpha1/decaf377_fmd.proto", + "../../proto/penumbra/penumbra/crypto/decaf377_frost/v1alpha1/decaf377_frost.proto", "../../proto/penumbra/penumbra/crypto/decaf377_rdsa/v1alpha1/decaf377_rdsa.proto", "../../proto/penumbra/penumbra/crypto/tct/v1alpha1/tct.proto", "../../proto/penumbra/penumbra/custody/v1alpha1/custody.proto",