Skip to main content

aptos_crypto_link/bls12381/
bls12381_keys.rs

1// Copyright (c) Aptos
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module provides APIs for private keys and public keys used in Boneh-Lynn-Shacham (BLS)
5//! aggregate signatures (including individual signatures and multisignatures) implemented on top of
6//! Barreto-Lynn-Scott BLS12-381 elliptic curves (https://github.com/supranational/blst).
7//!
8//! The `PublicKey` struct is used to represent both the public key of an individual signer
9//! as well as the aggregate public key of several signers. Before passing this struct as an
10//! argument, the caller should *always* verify its proof-of-possession (PoP) via
11//! `ProofOfPossession::verify`.
12//!
13//! The `PublicKey::aggregate` API assumes the caller has already verified
14//! proofs-of-possession for all the given public keys and therefore all public keys are valid,
15//! prime-order subgroup elements.
16//!
17//! In general, with the exception of `ProofOfPossession::verify` no library function should
18//! be given a public key as argument without first verifying that public key's PoP. Note that
19//! for aggregate public keys obtained via `PublicKey::aggregate` there is no PoP to verify, but
20//! the security assumption will be that all public keys given as input to this function have had
21//! their PoPs verified.
22
23use crate::{
24    bls12381, bls12381::DST_BLS_SIG_IN_G2_WITH_POP, hash::CryptoHash, signing_message, traits,
25    CryptoMaterialError, Genesis, Length, Uniform, ValidCryptoMaterial,
26    ValidCryptoMaterialStringExt, VerifyingKey,
27};
28use anyhow::{anyhow, Result};
29use aptos_crypto_derive::{DeserializeKey, SerializeKey, SilentDebug, SilentDisplay};
30use serde::Serialize;
31use std::{convert::TryFrom, fmt};
32
33#[derive(Clone, Eq, SerializeKey, DeserializeKey)]
34/// A BLS12381 public key
35pub struct PublicKey {
36    pub(crate) pubkey: blst::min_pk::PublicKey,
37    // NOTE: In order to minimize the size of this struct, we do not keep the PoP here.
38    // One reason for this is these PKs are stored in the root of the Merkle accumulator.
39}
40
41#[derive(SerializeKey, DeserializeKey, SilentDebug, SilentDisplay)]
42/// A BLS12381 private key
43pub struct PrivateKey {
44    pub(crate) privkey: blst::min_pk::SecretKey,
45}
46
47//////////////////////////////////////////////////////
48// Implementation of public-and-private key structs //
49//////////////////////////////////////////////////////
50
51impl PublicKey {
52    /// The length of a serialized PublicKey struct.
53    // NOTE: We have to hardcode this here because there is no library-defined constant.
54    pub const LENGTH: usize = 48;
55
56    /// Serialize a PublicKey.
57    pub fn to_bytes(&self) -> [u8; Self::LENGTH] {
58        self.pubkey.to_bytes()
59    }
60
61    /// Subgroup-checks the public key (i.e., verifies the public key is an element of the prime-order
62    /// subgroup and it is not the identity element).
63    ///
64    /// WARNING: Subgroup-checking is done implicitly when verifying the proof-of-possession (PoP) for
65    /// this public key  in `ProofOfPossession::verify`, so this function should not be called
66    /// separately for most use-cases. We leave it here just in case.
67    pub fn subgroup_check(&self) -> Result<()> {
68        self.pubkey.validate().map_err(|e| anyhow!("{:?}", e))
69    }
70
71    /// Aggregates the public keys of several signers into an aggregate public key, which can be later
72    /// used to verify a multisig aggregated from those signers.
73    ///
74    /// WARNING: This function assumes all public keys have had their proofs-of-possession verified
75    /// and have thus been group-checked.
76    pub fn aggregate(pubkeys: Vec<&Self>) -> Result<PublicKey> {
77        let blst_pubkeys: Vec<_> = pubkeys.iter().map(|pk| &pk.pubkey).collect();
78
79        // CRYPTONOTE(Alin): We assume the PKs have had their PoPs verified and thus have also been subgroup-checked
80        let aggpk = blst::min_pk::AggregatePublicKey::aggregate(&blst_pubkeys[..], false)
81            .map_err(|e| anyhow!("{:?}", e))?;
82
83        Ok(PublicKey {
84            pubkey: aggpk.to_public_key(),
85        })
86    }
87}
88
89impl PrivateKey {
90    /// The length of a serialized PrivateKey struct.
91    // NOTE: We have to hardcode this here because there is no library-defined constant
92    pub const LENGTH: usize = 32;
93
94    /// Serialize a PrivateKey.
95    pub fn to_bytes(&self) -> [u8; Self::LENGTH] {
96        self.privkey.to_bytes()
97    }
98}
99
100///////////////////////
101// PrivateKey Traits //
102///////////////////////
103
104impl traits::PrivateKey for PrivateKey {
105    type PublicKeyMaterial = PublicKey;
106}
107
108impl traits::SigningKey for PrivateKey {
109    type VerifyingKeyMaterial = PublicKey;
110    type SignatureMaterial = bls12381::Signature;
111
112    fn sign<T: CryptoHash + Serialize>(
113        &self,
114        message: &T,
115    ) -> Result<bls12381::Signature, CryptoMaterialError> {
116        Ok(bls12381::Signature {
117            sig: self
118                .privkey
119                .sign(&signing_message(message)?, DST_BLS_SIG_IN_G2_WITH_POP, &[]),
120        })
121    }
122
123    #[cfg(any(test, feature = "fuzzing"))]
124    fn sign_arbitrary_message(&self, message: &[u8]) -> bls12381::Signature {
125        bls12381::Signature {
126            sig: self.privkey.sign(message, DST_BLS_SIG_IN_G2_WITH_POP, &[]),
127        }
128    }
129}
130
131impl traits::ValidCryptoMaterial for PrivateKey {
132    fn to_bytes(&self) -> Vec<u8> {
133        self.to_bytes().to_vec()
134    }
135}
136
137impl Length for PrivateKey {
138    fn length(&self) -> usize {
139        Self::LENGTH
140    }
141}
142
143impl TryFrom<&[u8]> for PrivateKey {
144    type Error = CryptoMaterialError;
145
146    /// Deserializes a PrivateKey from a sequence of bytes.
147    fn try_from(bytes: &[u8]) -> std::result::Result<Self, CryptoMaterialError> {
148        Ok(Self {
149            privkey: blst::min_pk::SecretKey::from_bytes(bytes)
150                .map_err(|_| CryptoMaterialError::DeserializationError)?,
151        })
152    }
153}
154
155impl Uniform for PrivateKey {
156    fn generate<R>(rng: &mut R) -> Self
157    where
158        R: ::rand::RngCore + ::rand::CryptoRng,
159    {
160        // CRYPTONOTE(Alin): This "initial key material (IKM)" is the randomness used inside key_gen
161        // below to pseudo-randomly derive the secret key via an HKDF
162        // (see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature#section-2.3)
163        let mut ikm = [0u8; 32];
164        rng.fill_bytes(&mut ikm);
165        let privkey =
166            blst::min_pk::SecretKey::key_gen(&ikm, &[]).expect("ikm length should be higher");
167        Self { privkey }
168    }
169}
170
171impl Genesis for PrivateKey {
172    fn genesis() -> Self {
173        let mut buf = [0u8; Self::LENGTH];
174        buf[Self::LENGTH - 1] = 1;
175        Self::try_from(buf.as_ref()).unwrap()
176    }
177}
178
179#[cfg(feature = "assert-private-keys-not-cloneable")]
180static_assertions::assert_not_impl_any!(PrivateKey: Clone);
181
182#[cfg(any(test, feature = "cloneable-private-keys"))]
183impl Clone for PrivateKey {
184    fn clone(&self) -> Self {
185        let serialized: &[u8] = &(self.to_bytes());
186        PrivateKey::try_from(serialized).unwrap()
187    }
188}
189
190//////////////////////
191// PublicKey Traits //
192//////////////////////
193
194impl From<&PrivateKey> for PublicKey {
195    fn from(private_key: &PrivateKey) -> Self {
196        Self {
197            pubkey: private_key.privkey.sk_to_pk(),
198        }
199    }
200}
201
202impl traits::PublicKey for PublicKey {
203    type PrivateKeyMaterial = PrivateKey;
204}
205
206impl VerifyingKey for PublicKey {
207    type SigningKeyMaterial = PrivateKey;
208    type SignatureMaterial = bls12381::Signature;
209}
210
211impl ValidCryptoMaterial for PublicKey {
212    fn to_bytes(&self) -> Vec<u8> {
213        self.to_bytes().to_vec()
214    }
215}
216
217impl Length for PublicKey {
218    fn length(&self) -> usize {
219        Self::LENGTH
220    }
221}
222
223impl TryFrom<&[u8]> for PublicKey {
224    type Error = CryptoMaterialError;
225
226    /// Deserializes a PublicKey from a sequence of bytes.
227    ///
228    /// WARNING: Does NOT subgroup-check the public key! Instead, the caller is responsible for
229    /// verifying the public key's proof-of-possession (PoP) via `ProofOfPossession::verify`,
230    /// which implicitly subgroup-checks the public key.
231    ///
232    /// NOTE: This function will only check that the PK is a point on the curve:
233    ///  - `blst::min_pk::PublicKey::from_bytes(bytes)` calls `blst::min_pk::PublicKey::deserialize(bytes)`,
234    ///    which calls `$pk_deser` in https://github.com/supranational/blst/blob/711e1eec747772e8cae15d4a1885dd30a32048a4/bindings/rust/src/lib.rs#L734,
235    ///    which is mapped to `blst_p1_deserialize` in https://github.com/supranational/blst/blob/711e1eec747772e8cae15d4a1885dd30a32048a4/bindings/rust/src/lib.rs#L1652
236    ///  - `blst_p1_deserialize` eventually calls `POINTonE1_Deserialize_BE`, which checks
237    ///    the point is on the curve: https://github.com/supranational/blst/blob/711e1eec747772e8cae15d4a1885dd30a32048a4/src/e1.c#L296
238    fn try_from(bytes: &[u8]) -> std::result::Result<Self, CryptoMaterialError> {
239        Ok(Self {
240            pubkey: blst::min_pk::PublicKey::from_bytes(bytes)
241                .map_err(|_| CryptoMaterialError::DeserializationError)?,
242        })
243    }
244}
245
246impl std::hash::Hash for PublicKey {
247    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
248        let encoded_pubkey = self.to_bytes();
249        state.write(&encoded_pubkey);
250    }
251}
252
253// PartialEq trait implementation is required by the std::hash::Hash trait implementation above
254impl PartialEq for PublicKey {
255    fn eq(&self, other: &Self) -> bool {
256        self.to_bytes()[..] == other.to_bytes()[..]
257    }
258}
259
260impl fmt::Debug for PublicKey {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        write!(f, "{}", hex::encode(&self.to_bytes()))
263    }
264}
265
266impl fmt::Display for PublicKey {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        write!(f, "{}", hex::encode(&self.to_bytes()))
269    }
270}
271
272#[cfg(any(test, feature = "fuzzing"))]
273use crate::test_utils::KeyPair;
274#[cfg(any(test, feature = "fuzzing"))]
275use proptest::prelude::*;
276
277/// Produces a uniformly random BLS keypair from a seed
278#[cfg(any(test, feature = "fuzzing"))]
279pub fn keypair_strategy() -> impl Strategy<Value = KeyPair<PrivateKey, PublicKey>> {
280    crate::test_utils::uniform_keypair_strategy::<PrivateKey, PublicKey>()
281}
282
283#[cfg(any(test, feature = "fuzzing"))]
284impl proptest::arbitrary::Arbitrary for PublicKey {
285    type Parameters = ();
286    type Strategy = BoxedStrategy<Self>;
287
288    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
289        crate::test_utils::uniform_keypair_strategy::<PrivateKey, PublicKey>()
290            .prop_map(|v| v.public_key)
291            .boxed()
292    }
293}