Skip to main content

om_crypto_types/bls12381/
pop.rs

1//! Proof-of-Possession (PoP) Implementation
2//!
3//! Proofs-of-possession (PoPs) are used to prevent rogue-key attacks in BLS
4//! multisignatures and aggregate signatures.
5
6use std::{
7    fmt,
8    io::{Read, Write},
9};
10
11use blst::BLST_ERROR;
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13
14use crate::{
15    bls12381::{DST_BLS_POP_IN_G1, PrivateKey, PublicKey},
16    error::{CryptoError, CryptoResult},
17    io::{Export, Import},
18};
19
20/// A proof-of-possession (PoP) for a BLS private key
21///
22/// This is essentially a BLS signature on the public key itself,
23/// proving that the signer knows the private key corresponding to the public
24/// key.
25#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
26pub struct ProofOfPossession {
27    pub(crate) pop: [u8; Self::LENGTH],
28}
29
30impl Serialize for ProofOfPossession {
31    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32    where
33        S: Serializer,
34    {
35        if serializer.is_human_readable() {
36            serializer.serialize_str(&hex::encode(self.pop))
37        } else {
38            serializer.serialize_bytes(&self.pop)
39        }
40    }
41}
42
43impl<'de> Deserialize<'de> for ProofOfPossession {
44    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45    where
46        D: Deserializer<'de>,
47    {
48        if deserializer.is_human_readable() {
49            let s: String = String::deserialize(deserializer)?;
50            let bytes = hex::decode(s.trim_start_matches("0x")).map_err(serde::de::Error::custom)?;
51            Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
52        } else {
53            let bytes: Vec<u8> = Vec::deserialize(deserializer)?;
54            Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
55        }
56    }
57}
58
59impl ProofOfPossession {
60    /// The length of a serialized PoP (48 bytes, same as a signature)
61    pub const LENGTH: usize = 48;
62
63    /// Create a proof-of-possession for a private key
64    ///
65    /// This signs the public key with the private key using a different domain
66    /// separation tag than regular message signing.
67    pub fn create(private_key: &PrivateKey, public_key: &PublicKey) -> Self {
68        let pk_bytes = public_key.to_bytes();
69        let pop = private_key.key.sign(&pk_bytes, DST_BLS_POP_IN_G1, &[]);
70        Self { pop: pop.to_bytes() }
71    }
72
73    /// Verify a proof-of-possession
74    ///
75    /// This implicitly subgroup-checks both the PoP and the public key,
76    /// so the caller doesn't need to do it manually.
77    pub fn verify(&self, public_key: &PublicKey) -> CryptoResult<()> {
78        let pop = blst::min_sig::Signature::from_bytes(&self.pop)
79            .map_err(|_| CryptoError::ProofOfPossessionError("invalid PoP bytes".to_string()))?;
80
81        let pk = blst::min_sig::PublicKey::from_bytes(&public_key.key)
82            .map_err(|_| CryptoError::InvalidPublicKey("invalid BLS public key bytes".to_string()))?;
83
84        let pk_bytes = public_key.to_bytes();
85
86        // Verify with pk_validate=true to subgroup-check the public key
87        let result = pop.verify(true, &pk_bytes, DST_BLS_POP_IN_G1, &[], &pk, true);
88
89        if result == BLST_ERROR::BLST_SUCCESS {
90            Ok(())
91        } else {
92            Err(CryptoError::ProofOfPossessionError(format!(
93                "verification failed: {:?}",
94                result
95            )))
96        }
97    }
98
99    /// Serialize the PoP to bytes
100    pub fn to_bytes(&self) -> [u8; Self::LENGTH] {
101        self.pop
102    }
103
104    /// Deserialize a PoP from bytes
105    ///
106    /// WARNING: Does NOT subgroup-check the PoP! This is done implicitly during
107    /// verification.
108    pub fn from_bytes(bytes: &[u8]) -> CryptoResult<Self> {
109        if bytes.len() != Self::LENGTH {
110            return Err(CryptoError::ProofOfPossessionError("invalid PoP length".to_string()));
111        }
112        let pop = <[u8; Self::LENGTH]>::try_from(bytes)
113            .map_err(|_| CryptoError::ProofOfPossessionError("invalid PoP".to_string()))?;
114        Ok(Self { pop })
115    }
116
117    /// Subgroup-check the PoP
118    ///
119    /// WARNING: This is done implicitly during verification, so you typically
120    /// don't need to call this separately.
121    pub fn subgroup_check(&self) -> CryptoResult<()> {
122        let pop = blst::min_sig::Signature::from_bytes(&self.pop)
123            .map_err(|_| CryptoError::ProofOfPossessionError("invalid PoP bytes".to_string()))?;
124        pop.validate(true)
125            .map_err(|e| CryptoError::ProofOfPossessionError(format!("subgroup check failed: {:?}", e)))
126    }
127}
128
129impl fmt::Display for ProofOfPossession {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(f, "0x{}", hex::encode(self.pop))
132    }
133}
134
135impl Export<true> for ProofOfPossession {
136    fn export<W: Write>(&self, writer: &mut W) -> crate::io::Result<()> {
137        self.to_bytes().as_slice().export(writer)
138    }
139}
140
141impl Import<true> for ProofOfPossession {
142    fn import<R: Read>(reader: &mut R) -> crate::io::Result<Self> {
143        let bytes = Vec::<u8>::import(reader)?;
144        Self::from_bytes(&bytes)
145            .map_err(|e| crate::io::IoError::Other(format!("Failed to import BLS proof of possession: {}", e)))
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use rand::rngs::OsRng;
152
153    use super::*;
154
155    #[test]
156    fn test_pop_create_and_verify() {
157        let mut rng = OsRng;
158        let sk = PrivateKey::generate(&mut rng);
159        let pk = PublicKey::from(&sk);
160
161        let pop = ProofOfPossession::create(&sk, &pk);
162        assert!(pop.verify(&pk).is_ok());
163    }
164
165    #[test]
166    fn test_pop_verify_wrong_key() {
167        let mut rng = OsRng;
168        let sk1 = PrivateKey::generate(&mut rng);
169        let sk2 = PrivateKey::generate(&mut rng);
170        let pk1 = PublicKey::from(&sk1);
171        let pk2 = PublicKey::from(&sk2);
172
173        let pop1 = ProofOfPossession::create(&sk1, &pk1);
174
175        // PoP for pk1 should not verify against pk2
176        assert!(pop1.verify(&pk2).is_err());
177    }
178
179    #[test]
180    fn test_pop_serialization() {
181        let mut rng = OsRng;
182        let sk = PrivateKey::generate(&mut rng);
183        let pk = PublicKey::from(&sk);
184
185        let pop1 = ProofOfPossession::create(&sk, &pk);
186        let pop_bytes = pop1.to_bytes();
187        let pop2 = ProofOfPossession::from_bytes(&pop_bytes).unwrap();
188
189        assert_eq!(pop1, pop2);
190        assert!(pop2.verify(&pk).is_ok());
191    }
192
193    #[test]
194    fn test_pop_multiple_validators() {
195        let mut rng = OsRng;
196        let num_validators = 10;
197
198        for _ in 0..num_validators {
199            let sk = PrivateKey::generate(&mut rng);
200            let pk = PublicKey::from(&sk);
201            let pop = ProofOfPossession::create(&sk, &pk);
202
203            assert!(pop.verify(&pk).is_ok());
204        }
205    }
206}