1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0
//! This module provides APIs for _proofs-of-possesion (PoPs)_ used to prevent _rogue-key attacks_,
//! both for multisignatures and aggregate signatures.
//!
//! Rogue-key attacks were first introduced by Micali, Ohta and Reyzin [^MOR01] and PoPs were first
//! introduced by Ristenpart and Yilek [^RY07].
//!
//! [^MOR01]: Accountable-Subgroup Multisignatures: Extended Abstract; by Micali, Silvio and Ohta, Kazuo and Reyzin, Leonid; in Proceedings of the 8th ACM Conference on Computer and Communications Security; 2001;
//! [^RY07]: The Power of Proofs-of-Possession: Securing Multiparty Signatures against Rogue-Key Attacks; by Ristenpart, Thomas and Yilek, Scott; in Advances in Cryptology - EUROCRYPT 2007; 2007
use crate::{
bls12381::bls12381_keys::{PrivateKey, PublicKey},
CryptoMaterialError, Length, ValidCryptoMaterial, ValidCryptoMaterialStringExt,
};
use anyhow::{anyhow, Result};
use aptos_crypto_derive::{DeserializeKey, SerializeKey};
use blst::BLST_ERROR;
use std::convert::TryFrom;
/// Domain separation tag (DST) for hashing a public key before computing its proof-of-possesion (PoP),
/// which is also just a signature.
const DST_BLS_POP_IN_G2: &[u8] = b"BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
#[derive(Debug, Clone, SerializeKey, DeserializeKey)]
/// A proof-of-possesion (PoP) of a BLS12381 private key.
/// This is just a BLS signature on the corresponding public key.
pub struct ProofOfPossession {
pub(crate) pop: blst::min_pk::Signature,
}
impl ProofOfPossession {
/// The length of a serialized ProofOfPossession struct.
// NOTE: We have to hardcode this here because there is no library-defined constant
pub const LENGTH: usize = 96;
/// Serialize a ProofOfPossession.
pub fn to_bytes(&self) -> [u8; Self::LENGTH] {
self.pop.to_bytes()
}
/// Group-check the PoP (i.e., verifies the PoP is a valid group element).
///
/// WARNING: Group-checking is done implicitly in `verify` below, so this function need not be called
/// separately for most use-cases, as it incurs a performance penalty. We leave it here just in case.
pub fn group_check(&self) -> Result<()> {
self.pop.validate(true).map_err(|e| anyhow!("{:?}", e))
}
/// Verifies the proof-of-possesion (PoP) of the private key corresponding to the specified
/// BLS public key. Implicitly, group checks the PoP and the specified public key, so
/// the caller is not responsible for doing it manually.
pub fn verify(&self, pk: &PublicKey) -> Result<()> {
// CRYPTONOTE(Alin): We call the signature verification function with pk_validate set to true
// since we do not necessarily trust the PK we deserialized over the network whose PoP we are
// verifying here.
let result = self.pop.verify(
true,
&pk.to_bytes(),
DST_BLS_POP_IN_G2,
&[],
&pk.pubkey,
true,
);
if result == BLST_ERROR::BLST_SUCCESS {
Ok(())
} else {
Err(anyhow!(
"Proof-of-possession (PoP) did NOT verify: {:?}",
result
))
}
}
/// Creates a proof-of-possesion (PoP) of the specified BLS private key. This function
/// inefficiently recomputes the public key from the private key. To avoid this, the caller can
/// use `create_with_pubkey` instead, which accepts the public key as a second input.
pub fn create(sk: &PrivateKey) -> ProofOfPossession {
// CRYPTONOTE(Alin): The standard does not detail how the PK should be serialized for hashing purposes; we just do the obvious.
let pk = PublicKey {
pubkey: sk.privkey.sk_to_pk(),
};
ProofOfPossession::create_with_pubkey(sk, &pk)
}
/// Creates a proof-of-possesion (PoP) of the specified BLS private key. Takes the
/// corresponding public key as input, to avoid inefficiently recomputing it from the
/// private key.
///
/// WARNING: Does not group-check the PK, since this function will be typically called on
/// a freshly-generated key-pair or on a correctly-deserialized keypair.
pub fn create_with_pubkey(sk: &PrivateKey, pk: &PublicKey) -> ProofOfPossession {
// CRYPTONOTE(Alin): The standard does not detail how the PK should be serialized for hashing purposes; we just do the obvious.
let pk_bytes = pk.to_bytes();
// CRYPTONOTE(Alin): We hash with DST_BLS_POP_IN_G2 as per https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature#section-4.2.3
ProofOfPossession {
pop: sk.privkey.sign(&pk_bytes, DST_BLS_POP_IN_G2, &[]),
}
}
}
//////////////////////////////
// ProofOfPossession Traits //
//////////////////////////////
impl ValidCryptoMaterial for ProofOfPossession {
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
impl Length for ProofOfPossession {
fn length(&self) -> usize {
Self::LENGTH
}
}
impl TryFrom<&[u8]> for ProofOfPossession {
type Error = CryptoMaterialError;
/// Deserializes a BLS PoP from a sequence of bytes.
///
/// WARNING: Does NOT group-check the PoP! This is done implicitly when verifying the PoP in
/// `ProofOfPossession::verify`
fn try_from(bytes: &[u8]) -> std::result::Result<ProofOfPossession, CryptoMaterialError> {
Ok(Self {
pop: blst::min_pk::Signature::from_bytes(bytes)
.map_err(|_| CryptoMaterialError::DeserializationError)?,
})
}
}
impl std::hash::Hash for ProofOfPossession {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let encoded_signature = self.to_bytes();
state.write(&encoded_signature);
}
}