penumbra_sdk_custody/threshold/
config.rs

1use anyhow::Result;
2use decaf377::Fq;
3use decaf377_frost as frost;
4use ed25519_consensus::{SigningKey, VerificationKey};
5use penumbra_sdk_keys::{keys::NullifierKey, FullViewingKey};
6use rand_core::CryptoRngCore;
7use serde::{Deserialize, Serialize};
8use serde_with::{formats::Uppercase, hex::Hex, DisplayFromStr, TryFromInto};
9use std::collections::{HashMap, HashSet};
10
11/// A shim to serialize frost::keys::SigningShare
12#[serde_as]
13#[derive(Serialize, Deserialize)]
14struct SigningShareWrapper(#[serde_as(as = "Hex<Uppercase>")] Vec<u8>);
15
16impl From<frost::keys::SigningShare> for SigningShareWrapper {
17    fn from(value: frost::keys::SigningShare) -> Self {
18        Self(value.serialize())
19    }
20}
21
22impl TryFrom<SigningShareWrapper> for frost::keys::SigningShare {
23    type Error = anyhow::Error;
24
25    fn try_from(value: SigningShareWrapper) -> std::result::Result<Self, Self::Error> {
26        Ok(Self::deserialize(value.0)?)
27    }
28}
29
30/// A shim to serialize frost::keys::VerifyingShare
31#[serde_as]
32#[derive(Serialize, Deserialize)]
33struct VerifyingShareWrapper(#[serde_as(as = "Hex<Uppercase>")] Vec<u8>);
34
35impl From<frost::keys::VerifyingShare> for VerifyingShareWrapper {
36    fn from(value: frost::keys::VerifyingShare) -> Self {
37        Self(value.serialize())
38    }
39}
40
41impl TryFrom<VerifyingShareWrapper> for frost::keys::VerifyingShare {
42    type Error = anyhow::Error;
43
44    fn try_from(value: VerifyingShareWrapper) -> std::result::Result<Self, Self::Error> {
45        Ok(Self::deserialize(value.0)?)
46    }
47}
48
49/// A shim to serialize SigningKey
50#[serde_as]
51#[derive(Serialize, Deserialize)]
52struct SigningKeyWrapper(#[serde_as(as = "Hex<Uppercase>")] Vec<u8>);
53
54impl From<SigningKey> for SigningKeyWrapper {
55    fn from(value: SigningKey) -> Self {
56        Self(value.to_bytes().to_vec())
57    }
58}
59
60impl TryFrom<SigningKeyWrapper> for SigningKey {
61    type Error = anyhow::Error;
62
63    fn try_from(value: SigningKeyWrapper) -> std::result::Result<Self, Self::Error> {
64        Ok(Self::try_from(value.0.as_slice())?)
65    }
66}
67
68/// A shim to serialize VerifyingKey
69#[serde_as]
70#[derive(Serialize, Deserialize)]
71struct VerificationKeyWrapper(#[serde_as(as = "Hex<Uppercase>")] Vec<u8>);
72
73impl From<VerificationKey> for VerificationKeyWrapper {
74    fn from(value: VerificationKey) -> Self {
75        Self(value.to_bytes().to_vec())
76    }
77}
78
79impl TryFrom<VerificationKeyWrapper> for VerificationKey {
80    type Error = anyhow::Error;
81
82    fn try_from(value: VerificationKeyWrapper) -> std::result::Result<Self, Self::Error> {
83        Ok(Self::try_from(value.0.as_slice())?)
84    }
85}
86
87#[serde_as]
88#[derive(Clone, Debug, Serialize, Deserialize)]
89pub struct Config {
90    threshold: u16,
91    #[serde_as(as = "DisplayFromStr")]
92    fvk: FullViewingKey,
93    #[serde_as(as = "TryFromInto<SigningShareWrapper>")]
94    spend_key_share: frost::keys::SigningShare,
95    #[serde_as(as = "TryFromInto<SigningKeyWrapper>")]
96    signing_key: SigningKey,
97    #[serde_as(
98        as = "HashMap<TryFromInto<VerificationKeyWrapper>, TryFromInto<VerifyingShareWrapper>>"
99    )]
100    verifying_shares: HashMap<VerificationKey, frost::keys::VerifyingShare>,
101}
102
103impl PartialEq for Config {
104    fn eq(&self, other: &Self) -> bool {
105        self.threshold == other.threshold
106            && self.fvk == other.fvk
107            && self.spend_key_share == other.spend_key_share
108            // TIMING LEAK
109            && self.signing_key.as_bytes() == other.signing_key.as_bytes()
110            && self.verifying_shares == other.verifying_shares
111    }
112}
113
114impl Eq for Config {}
115
116impl Config {
117    /// Create a config from the parts that get spit out by the DKG protocol.
118    pub(crate) fn from_parts(
119        key_package: frost::keys::KeyPackage,
120        public_key_package: frost::keys::PublicKeyPackage,
121        signing_key: SigningKey,
122        verification_keys: Vec<VerificationKey>,
123        nullifier_key: Fq,
124    ) -> Self {
125        let fvk = FullViewingKey::from_components(
126            public_key_package
127                .group_public()
128                .serialize()
129                .as_slice()
130                .try_into()
131                .expect("conversion of a group element to a VerifyingKey should not fail"),
132            NullifierKey(nullifier_key),
133        );
134        let spend_key_share = key_package.secret_share().to_owned();
135        let verifying_shares = verification_keys
136            .into_iter()
137            .map(|vk| {
138                let id = frost::Identifier::derive(vk.as_bytes().as_slice())
139                    .expect("deriving identifiers should not fail");
140                (vk, public_key_package.signer_pubkeys()[&id])
141            })
142            .collect();
143        Self {
144            threshold: *key_package.min_signers(),
145            fvk,
146            spend_key_share,
147            signing_key,
148            verifying_shares,
149        }
150    }
151
152    pub fn deal(mut rng: &mut impl CryptoRngCore, t: u16, n: u16) -> Result<Vec<Self>> {
153        let signing_keys = (0..n)
154            .map(|_| {
155                let sk = SigningKey::new(&mut rng);
156                let pk = sk.verification_key();
157                (pk, sk)
158            })
159            .collect::<HashMap<_, _>>();
160        let identifiers = signing_keys
161            .keys()
162            .cloned()
163            .map(|pk| Ok((pk, frost::Identifier::derive(pk.as_bytes().as_slice())?)))
164            .collect::<Result<HashMap<_, _>, frost::Error>>()?;
165
166        let (share_map, public_key_package) = frost::keys::generate_with_dealer(
167            n,
168            t,
169            frost::keys::IdentifierList::Custom(
170                identifiers.values().cloned().collect::<Vec<_>>().as_slice(),
171            ),
172            &mut rng,
173        )?;
174        let verifying_shares = signing_keys
175            .keys()
176            .map(|pk| {
177                let identifier = frost::Identifier::derive(pk.to_bytes().as_slice())
178                    .expect("should be able to derive identifier");
179                (pk.clone(), public_key_package.signer_pubkeys()[&identifier])
180            })
181            .collect::<HashMap<_, _>>();
182        // Okay, this conversion is a bit of a hack, but it should work...
183        // It's a hack cause we're going via the serialization, but, you know, that should be fine.
184        let fvk = FullViewingKey::from_components(
185            public_key_package
186                .group_public()
187                .serialize()
188                .as_slice()
189                .try_into()
190                .expect("conversion of a group element to a VerifyingKey should not fail"),
191            NullifierKey(Fq::rand(rng)),
192        );
193
194        Ok(signing_keys
195            .into_iter()
196            .map(|(verification_key, signing_key)| {
197                let identifier = identifiers[&verification_key];
198                let signing_share = share_map[&identifier].value().clone();
199                Self {
200                    threshold: t,
201                    signing_key,
202                    fvk: fvk.clone(),
203                    spend_key_share: signing_share,
204                    verifying_shares: verifying_shares.clone(),
205                }
206            })
207            .collect())
208    }
209
210    pub fn threshold(&self) -> u16 {
211        self.threshold
212    }
213
214    fn group_public(&self) -> frost::keys::VerifyingKey {
215        frost::keys::VerifyingKey::deserialize(
216            self.fvk.spend_verification_key().to_bytes().to_vec(),
217        )
218        .expect("should be able to parse out VerifyingKey from FullViewingKey")
219    }
220
221    pub fn key_package(&self) -> frost::keys::KeyPackage {
222        let identifier =
223            frost::Identifier::derive(&self.signing_key.verification_key().as_bytes().as_slice())
224                .expect("deriving our identifier should not fail");
225
226        frost::keys::KeyPackage::new(
227            identifier,
228            self.spend_key_share,
229            self.spend_key_share.into(),
230            self.group_public(),
231            self.threshold,
232        )
233    }
234
235    pub fn public_key_package(&self) -> frost::keys::PublicKeyPackage {
236        let signer_pubkeys = self
237            .verifying_shares
238            .iter()
239            .map(|(vk, share)| {
240                (
241                    frost::Identifier::derive(vk.to_bytes().as_slice())
242                        .expect("deriving an identifier should not fail"),
243                    share.clone(),
244                )
245            })
246            .collect();
247        frost::keys::PublicKeyPackage::new(signer_pubkeys, self.group_public())
248    }
249
250    pub fn signing_key(&self) -> &SigningKey {
251        &self.signing_key
252    }
253
254    pub fn fvk(&self) -> &FullViewingKey {
255        &self.fvk
256    }
257
258    pub fn verification_keys(&self) -> HashSet<VerificationKey> {
259        self.verifying_shares.keys().cloned().collect()
260    }
261}
262
263#[cfg(test)]
264mod test {
265    use rand_core::OsRng;
266
267    use super::*;
268
269    #[test]
270    fn test_config_serialization_roundtrip() -> Result<()> {
271        // You can't put 1, because no FUN is allowed
272        let config = Config::deal(&mut OsRng, 2, 2)?.pop().unwrap();
273        let config_str = serde_json::to_string(&config)?;
274        let config2: Config = serde_json::from_str(&config_str)?;
275        // Can't derive partial eq, so go field by field
276        assert_eq!(config.threshold, config2.threshold);
277        assert_eq!(config.fvk, config2.fvk);
278        assert_eq!(config.spend_key_share, config2.spend_key_share);
279        assert_eq!(config.verifying_shares, config2.verifying_shares);
280        Ok(())
281    }
282}