diff --git a/bls-signatures/benches/bls_signatures.rs b/bls-signatures/benches/bls_signatures.rs index 99a54b419..9e405618a 100644 --- a/bls-signatures/benches/bls_signatures.rs +++ b/bls-signatures/benches/bls_signatures.rs @@ -112,15 +112,15 @@ fn bench_key_generation(c: &mut Criterion) { // Benchmark for creating and verifying a proof of possession fn bench_proof_of_possession(c: &mut Criterion) { let keypair = Keypair::new(); - let pop = keypair.proof_of_possession(); + let pop = keypair.proof_of_possession(None); c.bench_function("proof_of_possession_creation", |b| { - b.iter(|| black_box(keypair.proof_of_possession())); + b.iter(|| black_box(keypair.proof_of_possession(None))); }); c.bench_function("proof_of_possession_verification", |b| { b.iter(|| { - black_box(keypair.public.verify_proof_of_possession(&pop)).unwrap(); + black_box(keypair.public.verify_proof_of_possession(&pop, None)).unwrap(); }) }); } diff --git a/bls-signatures/src/hash.rs b/bls-signatures/src/hash.rs index f4c2cc097..169413d1c 100644 --- a/bls-signatures/src/hash.rs +++ b/bls-signatures/src/hash.rs @@ -15,7 +15,14 @@ pub fn hash_message_to_point(message: &[u8]) -> G2Projective { } /// Hash a pubkey to a G2 point -pub(crate) fn hash_pubkey_to_g2(public_key: &PubkeyProjective) -> G2Projective { - let pubkey_bytes = public_key.0.to_compressed(); - G2Projective::hash_to_curve(&pubkey_bytes, POP_DST, &[]) +pub(crate) fn hash_pubkey_to_g2( + public_key: &PubkeyProjective, + payload: Option<&[u8]>, +) -> G2Projective { + if let Some(bytes) = payload { + G2Projective::hash_to_curve(bytes, POP_DST, &[]) + } else { + let public_key_bytes = public_key.0.to_compressed(); + G2Projective::hash_to_curve(&public_key_bytes, POP_DST, &[]) + } } diff --git a/bls-signatures/src/keypair.rs b/bls-signatures/src/keypair.rs index 1b6d29d15..869c4357b 100644 --- a/bls-signatures/src/keypair.rs +++ b/bls-signatures/src/keypair.rs @@ -53,8 +53,8 @@ impl Keypair { } /// Generate a proof of possession for the given keypair - pub fn proof_of_possession(&self) -> ProofOfPossessionProjective { - self.secret.proof_of_possession() + pub fn proof_of_possession(&self, payload: Option<&[u8]>) -> ProofOfPossessionProjective { + self.secret.proof_of_possession(payload) } /// Sign a message using the provided secret key diff --git a/bls-signatures/src/proof_of_possession.rs b/bls-signatures/src/proof_of_possession.rs index e6c46366a..d5b77a29a 100644 --- a/bls-signatures/src/proof_of_possession.rs +++ b/bls-signatures/src/proof_of_possession.rs @@ -18,7 +18,7 @@ use { /// Domain separation tag used when hashing public keys to G2 in the proof of /// possession signing and verification functions. See the /// [standard](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#section-4.2.3). -pub const POP_DST: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_"; +pub const POP_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; /// Size of a BLS proof of possession in a compressed point representation pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE: usize = 96; @@ -50,9 +50,13 @@ pub trait AsProofOfPossession { #[cfg(not(target_os = "solana"))] pub trait VerifiableProofOfPossession: AsProofOfPossessionProjective { /// Verifies the proof of possession against any convertible public key type. - fn verify(&self, pubkey: &P) -> Result { + fn verify( + &self, + pubkey: &P, + payload: Option<&[u8]>, + ) -> Result { let proof_projective = self.try_as_projective()?; - pubkey.verify_proof_of_possession(&proof_projective) + pubkey.verify_proof_of_possession(&proof_projective, payload) } } @@ -172,7 +176,7 @@ mod tests { #[test] fn test_proof_of_possession() { let keypair = Keypair::new(); - let proof_projective = keypair.proof_of_possession(); + let proof_projective = keypair.proof_of_possession(None); let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); let pubkey_affine: Pubkey = keypair.public; @@ -181,17 +185,17 @@ mod tests { let proof_affine: ProofOfPossession = proof_projective.into(); let proof_compressed: ProofOfPossessionCompressed = proof_affine.try_into().unwrap(); - assert!(proof_projective.verify(&pubkey_projective).unwrap()); - assert!(proof_affine.verify(&pubkey_projective).unwrap()); - assert!(proof_compressed.verify(&pubkey_projective).unwrap()); + assert!(proof_projective.verify(&pubkey_projective, None).unwrap()); + assert!(proof_affine.verify(&pubkey_projective, None).unwrap()); + assert!(proof_compressed.verify(&pubkey_projective, None).unwrap()); - assert!(proof_projective.verify(&pubkey_affine).unwrap()); - assert!(proof_affine.verify(&pubkey_affine).unwrap()); - assert!(proof_compressed.verify(&pubkey_affine).unwrap()); + assert!(proof_projective.verify(&pubkey_affine, None).unwrap()); + assert!(proof_affine.verify(&pubkey_affine, None).unwrap()); + assert!(proof_compressed.verify(&pubkey_affine, None).unwrap()); - assert!(proof_projective.verify(&pubkey_compressed).unwrap()); - assert!(proof_affine.verify(&pubkey_compressed).unwrap()); - assert!(proof_compressed.verify(&pubkey_compressed).unwrap()); + assert!(proof_projective.verify(&pubkey_compressed, None).unwrap()); + assert!(proof_affine.verify(&pubkey_compressed, None).unwrap()); + assert!(proof_compressed.verify(&pubkey_compressed, None).unwrap()); } #[test] @@ -240,4 +244,41 @@ mod tests { let deserialized: ProofOfPossessionCompressed = bincode::deserialize(&serialized).unwrap(); assert_eq!(original, deserialized); } + + #[test] + fn test_proof_of_possession_with_custom_payload() { + let keypair = Keypair::new(); + let custom_payload = b"SIMD-0387-context-data"; + + let proof_custom = keypair.proof_of_possession(Some(custom_payload)); + assert!(keypair + .public + .verify_proof_of_possession(&proof_custom, Some(custom_payload)) + .unwrap()); + + assert!(!keypair + .public + .verify_proof_of_possession(&proof_custom, None) // try verify with `None` + .unwrap()); + + let wrong_payload = b"wrong-context"; + assert!(!keypair + .public + // try verify with wrong payload + .verify_proof_of_possession(&proof_custom, Some(wrong_payload)) + .unwrap()); + + // verify standard PoP behavior + let proof_standard = keypair.proof_of_possession(None); + // standard passes with None + assert!(keypair + .public + .verify_proof_of_possession(&proof_standard, None) + .unwrap()); + // standard fails with custom payload + assert!(!keypair + .public + .verify_proof_of_possession(&proof_standard, Some(custom_payload)) + .unwrap()); + } } diff --git a/bls-signatures/src/pubkey.rs b/bls-signatures/src/pubkey.rs index 52ac626df..94f226d77 100644 --- a/bls-signatures/src/pubkey.rs +++ b/bls-signatures/src/pubkey.rs @@ -75,10 +75,11 @@ pub trait VerifiablePubkey: AsPubkey { fn verify_proof_of_possession( &self, proof: &P, + payload: Option<&[u8]>, ) -> Result { let pubkey_affine = self.try_as_affine()?; let proof_affine = proof.try_as_affine()?; - Ok(pubkey_affine._verify_proof_of_possession(&proof_affine)) + Ok(pubkey_affine._verify_proof_of_possession(&proof_affine, payload)) } } @@ -274,7 +275,11 @@ impl Pubkey { } /// Verify a proof of possession against a public key - pub(crate) fn _verify_proof_of_possession(&self, proof: &ProofOfPossession) -> bool { + pub(crate) fn _verify_proof_of_possession( + &self, + proof: &ProofOfPossession, + payload: Option<&[u8]>, + ) -> bool { let Some(pubkey_affine): Option = G1Affine::from_uncompressed(&self.0).into() else { return false; @@ -289,7 +294,7 @@ impl Pubkey { // The verification equation is e(pubkey, H(pubkey)) == e(g1, proof). // This is rewritten to e(pubkey, H(pubkey)) * e(-g1, proof) = 1 for batching. - let hashed_pubkey_affine: G2Affine = hash_pubkey_to_g2(&pubkey_projective).into(); + let hashed_pubkey_affine: G2Affine = hash_pubkey_to_g2(&pubkey_projective, payload).into(); let hashed_pubkey_prepared = G2Prepared::from(hashed_pubkey_affine); let proof_prepared = G2Prepared::from(proof_affine); @@ -407,7 +412,7 @@ mod tests { #[test] fn test_pubkey_verify_proof_of_possession() { let keypair = Keypair::new(); - let proof_projective = keypair.proof_of_possession(); + let proof_projective = keypair.proof_of_possession(None); let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); let pubkey_affine: Pubkey = pubkey_projective.into(); @@ -417,33 +422,33 @@ mod tests { let proof_compressed: ProofOfPossessionCompressed = proof_affine.try_into().unwrap(); assert!(pubkey_projective - .verify_proof_of_possession(&proof_projective) + .verify_proof_of_possession(&proof_projective, None) .unwrap()); assert!(pubkey_affine - .verify_proof_of_possession(&proof_projective) + .verify_proof_of_possession(&proof_projective, None) .unwrap()); assert!(pubkey_compressed - .verify_proof_of_possession(&proof_projective) + .verify_proof_of_possession(&proof_projective, None) .unwrap()); assert!(pubkey_projective - .verify_proof_of_possession(&proof_affine) + .verify_proof_of_possession(&proof_affine, None) .unwrap()); assert!(pubkey_affine - .verify_proof_of_possession(&proof_affine) + .verify_proof_of_possession(&proof_affine, None) .unwrap()); assert!(pubkey_compressed - .verify_proof_of_possession(&proof_affine) + .verify_proof_of_possession(&proof_affine, None) .unwrap()); assert!(pubkey_projective - .verify_proof_of_possession(&proof_compressed) + .verify_proof_of_possession(&proof_compressed, None) .unwrap()); assert!(pubkey_affine - .verify_proof_of_possession(&proof_compressed) + .verify_proof_of_possession(&proof_compressed, None) .unwrap()); assert!(pubkey_compressed - .verify_proof_of_possession(&proof_compressed) + .verify_proof_of_possession(&proof_compressed, None) .unwrap()); } diff --git a/bls-signatures/src/secret_key.rs b/bls-signatures/src/secret_key.rs index 789cd6129..d56b72936 100644 --- a/bls-signatures/src/secret_key.rs +++ b/bls-signatures/src/secret_key.rs @@ -67,10 +67,10 @@ impl SecretKey { /// Generate a proof of possession for the corresponding pubkey #[allow(clippy::arithmetic_side_effects)] - pub fn proof_of_possession(&self) -> ProofOfPossessionProjective { + pub fn proof_of_possession(&self, payload: Option<&[u8]>) -> ProofOfPossessionProjective { let pubkey = PubkeyProjective::from_secret(self); - let hashed_pubkey_bytes = hash_pubkey_to_g2(&pubkey); - ProofOfPossessionProjective(hashed_pubkey_bytes * self.0) + let hashed_point = hash_pubkey_to_g2(&pubkey, payload); + ProofOfPossessionProjective(hashed_point * self.0) } /// Sign a message using the provided secret key