penumbra_sdk_custody/threshold/
dkg.rs1use anyhow::{anyhow, Result};
2use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
3use decaf377::Fq;
4use decaf377_frost as frost;
5use frost::keys::dkg as frost_dkg;
6use std::collections::{HashMap, HashSet};
7mod encryption;
8use ed25519_consensus::{Signature, SigningKey, VerificationKey};
9use encryption::EncryptionKey;
10use penumbra_sdk_proto::{custody::threshold::v1 as pb, DomainType, Message};
11use rand_core::CryptoRngCore;
12
13use self::encryption::DecryptionKey;
14
15use super::Config;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22struct NullifierCommitment([u8; 32]);
23
24impl NullifierCommitment {
25 fn create(share: Fq) -> Self {
27 let mut state = blake2b_simd::Params::new()
28 .personal(b"dkg-commit")
29 .to_state();
30 share
31 .serialize_compressed(&mut state)
32 .expect("failed to serialize Fq element");
33 let out = state.finalize().as_array()[..32]
34 .try_into()
35 .expect("array conversion should not fail");
36 Self(out)
37 }
38
39 fn as_bytes(&self) -> &[u8; 32] {
40 &self.0
41 }
42}
43
44impl TryFrom<&[u8]> for NullifierCommitment {
45 type Error = anyhow::Error;
46
47 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
48 let bytes: [u8; 32] = value.try_into()?;
49 Ok(Self(bytes))
50 }
51}
52
53#[derive(Clone)]
55pub struct Round1 {
56 package: frost_dkg::round1::Package,
58 nullifier_commitment: NullifierCommitment,
60 epk: EncryptionKey,
62 vk: VerificationKey,
64}
65
66impl From<Round1> for pb::DkgRound1 {
67 fn from(value: Round1) -> Self {
68 Self {
69 pkg: Some(value.package.into()),
70 nullifier_commitment: value.nullifier_commitment.as_bytes().to_vec(),
71 epk: value.epk.as_bytes().to_vec(),
72 vk: value.vk.as_bytes().to_vec(),
73 }
74 }
75}
76
77impl TryFrom<pb::DkgRound1> for Round1 {
78 type Error = anyhow::Error;
79
80 fn try_from(value: pb::DkgRound1) -> std::result::Result<Self, Self::Error> {
81 Ok(Self {
82 package: value
83 .pkg
84 .ok_or(anyhow!("DkgRound1 missing pkg"))?
85 .try_into()?,
86 nullifier_commitment: value.nullifier_commitment.as_slice().try_into()?,
87 epk: value.epk.as_slice().try_into()?,
88 vk: value.vk.as_slice().try_into()?,
89 })
90 }
91}
92
93impl DomainType for Round1 {
94 type Proto = pb::DkgRound1;
95}
96
97fn round2_inner_to_pb(
98 encrypted_packages: HashMap<VerificationKey, Vec<u8>>,
99 nullifier: Fq,
100) -> pb::dkg_round2::Inner {
101 let nullifier = {
102 let mut bytes = Vec::new();
103 nullifier
104 .serialize_compressed(&mut bytes)
105 .expect("field serialization should not fail");
106 bytes
107 };
108 let encrypted_packages = {
110 let mut acc: Vec<_> = encrypted_packages
111 .into_iter()
112 .map(|(k, v)| pb::dkg_round2::TargetedPackage {
113 vk: k.as_bytes().to_vec(),
114 encrypted_package: v,
115 })
116 .collect();
117 acc.sort_by_key(|x| x.vk.clone());
118 acc
119 };
120 pb::dkg_round2::Inner {
121 encrypted_packages,
122 nullifier,
123 }
124}
125
126#[derive(Clone, Debug)]
128pub struct Round2 {
129 encrypted_packages: HashMap<VerificationKey, Vec<u8>>,
131 nullifier: Fq,
133 vk: VerificationKey,
135 sig: Signature,
137}
138
139impl From<Round2> for pb::DkgRound2 {
140 fn from(value: Round2) -> Self {
141 Self {
142 inner: Some(round2_inner_to_pb(
143 value.encrypted_packages,
144 value.nullifier,
145 )),
146 vk: value.vk.as_bytes().to_vec(),
147 sig: value.sig.to_bytes().to_vec(),
148 }
149 }
150}
151
152impl TryFrom<pb::DkgRound2> for Round2 {
153 type Error = anyhow::Error;
154
155 fn try_from(value: pb::DkgRound2) -> std::result::Result<Self, Self::Error> {
156 let inner = value.inner.ok_or(anyhow!("DkgRound2 missing inner"))?;
157 Ok(Self {
158 encrypted_packages: inner
159 .encrypted_packages
160 .into_iter()
161 .map(|x| Ok((x.vk.as_slice().try_into()?, x.encrypted_package)))
162 .collect::<Result<HashMap<_, _>, Self::Error>>()?,
163 nullifier: Fq::deserialize_compressed(inner.nullifier.as_slice())?,
164 vk: value.vk.as_slice().try_into()?,
165 sig: value.sig.as_slice().try_into()?,
166 })
167 }
168}
169
170impl DomainType for Round2 {
171 type Proto = pb::DkgRound2;
172}
173
174impl Round2 {
175 fn make(
176 sk: &SigningKey,
177 encrypted_packages: HashMap<VerificationKey, Vec<u8>>,
178 nullifier: Fq,
179 ) -> Self {
180 let data = round2_inner_to_pb(encrypted_packages.clone(), nullifier).encode_to_vec();
181 let sig = sk.sign(&data);
182 Self {
183 encrypted_packages,
184 nullifier,
185 vk: sk.verification_key(),
186 sig,
187 }
188 }
189
190 fn encrypted_packages(self) -> Result<(VerificationKey, HashMap<VerificationKey, Vec<u8>>)> {
191 let data =
192 round2_inner_to_pb(self.encrypted_packages.clone(), self.nullifier).encode_to_vec();
193 self.vk.verify(&self.sig, &data)?;
194 Ok((self.vk, self.encrypted_packages))
195 }
196}
197
198pub struct Round1State {
200 secret_package: frost_dkg::round1::SecretPackage,
202 nullifier: Fq,
204 sk: SigningKey,
206 edk: DecryptionKey,
208}
209
210pub struct Round2State {
212 secret_package: frost_dkg::round2::SecretPackage,
214 round1_packages: HashMap<frost::Identifier, frost_dkg::round1::Package>,
216 associated_data: HashMap<VerificationKey, (frost::Identifier, NullifierCommitment)>,
221 nullifier: Fq,
223 sk: SigningKey,
225 edk: DecryptionKey,
227}
228
229pub fn round1(mut rng: impl CryptoRngCore, t: u16, n: u16) -> Result<(Round1, Round1State)> {
230 let sk = SigningKey::new(&mut rng);
232 let vk = sk.verification_key();
233 let id = frost::Identifier::derive(vk.as_bytes())?;
234 let (secret_package, package) = frost_dkg::part1(id, n, t, &mut rng)?;
235 let edk = DecryptionKey::new(&mut rng);
236 let epk = edk.public();
237 let nullifier = Fq::rand(&mut rng);
238 let nullifier_commitment = NullifierCommitment::create(nullifier);
239 let round1 = Round1 {
240 package,
241 nullifier_commitment,
242 epk,
243 vk,
244 };
245 let state = Round1State {
246 secret_package,
247 nullifier,
248 sk,
249 edk,
250 };
251 Ok((round1, state))
252}
253
254pub fn round2(
255 mut rng: impl CryptoRngCore,
256 state: Round1State,
257 messages: Vec<Round1>,
258) -> Result<(Round2, Round2State)> {
259 {
261 let mut seen = HashSet::new();
262 seen.insert(state.sk.verification_key());
263 for m in &messages {
264 if seen.contains(&m.vk) {
265 anyhow::bail!("duplicate verification key in messages");
266 }
267 }
268 }
269
270 let associated_info = messages
271 .into_iter()
272 .map(|x| {
273 Ok((
274 x.vk,
275 (
276 frost::Identifier::derive(x.vk.as_bytes())?,
277 x.epk,
278 x.package,
279 x.nullifier_commitment,
280 ),
281 ))
282 })
283 .collect::<Result<HashMap<_, _>>>()?;
284 let round1_packages = associated_info
285 .iter()
286 .map(|(_, (id, _, package, _))| (*id, package.clone()))
287 .collect();
288 let (secret_package, output_packages) =
289 frost_dkg::part2(state.secret_package, &round1_packages)?;
290 let round2 = {
291 let encrypted_packages = associated_info
292 .iter()
293 .map(|(vk, (id, epk, _, _))| {
294 let package = output_packages
295 .get(id)
296 .ok_or(anyhow!("unknown identifier: {:?}", id))?;
297 let ciphertext = epk.encrypt(&mut rng, &package.encode_to_vec());
298 Ok((vk.clone(), ciphertext))
299 })
300 .collect::<Result<HashMap<_, _>>>()?;
301 Round2::make(&state.sk, encrypted_packages, state.nullifier)
302 };
303 let state = Round2State {
304 secret_package,
305 round1_packages,
306 associated_data: associated_info
307 .into_iter()
308 .map(|(vk, (id, _, _, com))| (vk, (id, com)))
309 .collect(),
310 nullifier: state.nullifier,
311 sk: state.sk,
312 edk: state.edk,
313 };
314 Ok((round2, state))
315}
316
317pub fn round3(
318 mut rng: impl CryptoRngCore,
319 state: Round2State,
320 messages: Vec<Round2>,
321) -> Result<Config> {
322 let nullifier_key = {
323 let mut acc = state.nullifier;
324 for message in &messages {
325 if !state.associated_data.contains_key(&message.vk) {
326 anyhow::bail!("unknown verification key in round 2 message");
327 }
328 if NullifierCommitment::create(message.nullifier)
329 != state.associated_data[&message.vk].1
330 {
331 anyhow::bail!("opened nullifier did not match commitment");
332 }
333 acc += message.nullifier;
334 }
335 acc
336 };
337 let round2_packages = messages
338 .into_iter()
339 .map(|x| {
340 let (vk, encrypted_packages) = x.encrypted_packages()?;
341 let my_ciphertext = encrypted_packages
342 .get(&state.sk.verification_key())
343 .ok_or(anyhow!("no encrypted package for this recipient"))?;
344 let my_plaintext = state.edk.decrypt(&mut rng, &my_ciphertext)?;
345 let package = frost_dkg::round2::Package::decode(my_plaintext.as_slice())?;
346 let id = state
347 .associated_data
348 .get(&vk)
349 .ok_or(anyhow!("unknown verification key in round 2 message"))?
350 .0;
351 Ok((id, package))
352 })
353 .collect::<Result<HashMap<_, _>>>()?;
354 let (key_package, public_key_package) = frost_dkg::part3(
355 &state.secret_package,
356 &state.round1_packages,
357 &round2_packages,
358 )?;
359 let verification_keys = {
360 let mut acc: Vec<_> = state.associated_data.keys().cloned().collect();
361 acc.push(state.sk.verification_key());
362 acc
363 };
364 Ok(Config::from_parts(
365 key_package,
366 public_key_package,
367 state.sk,
368 verification_keys,
369 nullifier_key,
370 ))
371}