Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change validator key to BLS12-381 #103

Merged
merged 8 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 142 additions & 147 deletions node/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions node/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ skip = [

# Old versions required by jsonrpsee.
{ name = "base64", version = "0.13.1" },
{ name = "base64", version = "0.21.7" },
{ name = "block-buffer", version = "0.9.0" },
{ name = "digest", version = "0.10.7" },

Expand Down
1 change: 1 addition & 0 deletions node/libs/concurrency/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
pub type Duration = time::Duration;

/// Monotonic clock time.
#[allow(deprecated)]
pub type Instant = time::Instant;

/// UTC time in nanoseconds precision.
Expand Down
2 changes: 1 addition & 1 deletion node/libs/crypto/benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn bench_bls12_381(c: &mut Criterion) {
let pks: Vec<PublicKey> = sks.iter().map(|k| k.public()).collect();
let msg = rng.gen::<[u8; 32]>();
let sigs: Vec<Signature> = sks.iter().map(|k| k.sign(&msg)).collect();
let agg = AggregateSignature::aggregate(&sigs)?;
let agg = AggregateSignature::aggregate(&sigs);
agg.verify(pks.iter().map(|pk| (&msg[..], pk)))
});
});
Expand Down
139 changes: 78 additions & 61 deletions node/libs/crypto/src/bls12_381/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
//! is sufficient).

use crate::ByteFmt;
use anyhow::{anyhow, bail};
use blst::{min_pk as bls, BLST_ERROR};
use anyhow::anyhow;
use blst::{min_sig as bls, BLST_ERROR};
use rand::Rng as _;
use std::collections::BTreeMap;
use std::{
collections::BTreeMap,
fmt::{Debug, Formatter},
};
use zeroize::ZeroizeOnDrop;

#[cfg(test)]
Expand All @@ -25,11 +28,11 @@ pub const DST: &[u8] = b"MATTER_LABS_CONSENSUS_BLS_SIG_BLS12381G2_XMD:SHA-256_SS
/// The domain separation tag for the proof of possession.
pub const DST_POP: &[u8] = b"MATTER_LABS_CONSENSUS_BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_";

/// The byte-length of a BLS public key when serialized in compressed form.
pub const PUBLIC_KEY_BYTES_LEN: usize = 48;
/// The byte-length of a BLS signature when serialized in compressed form.
pub const SIGNATURE_BYTES_LEN: usize = 48;

/// Represents the public key at infinity.
pub const INFINITY_PUBLIC_KEY: [u8; PUBLIC_KEY_BYTES_LEN] = [
/// Represents the signature at infinity.
pub const INFINITY_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [
0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
Expand Down Expand Up @@ -76,6 +79,18 @@ impl ByteFmt for SecretKey {
}
}

impl Debug for SecretKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "SecretKey({:?})", self.public())
}
}

impl PartialEq for SecretKey {
fn eq(&self, other: &Self) -> bool {
self.public() == other.public()
}
}

/// Type safety wrapper around a `blst` public key.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKey(bls::PublicKey);
Expand All @@ -87,13 +102,11 @@ impl std::hash::Hash for PublicKey {
}

impl ByteFmt for PublicKey {
/// This method also checks if the public key is not infinity and if it is in the correct subgroup.
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
if bytes == INFINITY_PUBLIC_KEY {
bail!(Error::InvalidInfinityPublicKey)
}
bls::PublicKey::from_bytes(bytes)
bls::PublicKey::key_validate(bytes)
.map(Self)
.map_err(|err| anyhow!("Error decoding public key: {err:?}"))
.map_err(|e| anyhow!("Failed to decode public key: {e:?}"))
}

fn encode(&self) -> Vec<u8> {
Expand All @@ -119,19 +132,20 @@ pub struct Signature(bls::Signature);

impl Signature {
/// Verifies a signature against the provided public key
pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> Result<(), Error> {
pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> anyhow::Result<()> {
let result = self.0.verify(true, msg, DST, &[], &pk.0, true);

match result {
BLST_ERROR::BLST_SUCCESS => Ok(()),
err => Err(Error::SignatureVerification(err)),
err => Err(anyhow!("Signature verification failure: {err:?}")),
brunoffranca marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

impl ByteFmt for Signature {
/// This method also checks if the signature is in the correct subgroup.
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
bls::Signature::from_bytes(bytes)
bls::Signature::sig_validate(bytes, false)
.map(Self)
.map_err(|err| anyhow!("Error decoding signature: {err:?}"))
}
Expand All @@ -153,22 +167,31 @@ impl Ord for Signature {
}
}

/// Type safety wrapper around a `blst` signature indicating that it is an aggregated signature
///
/// Due to the `blst` aggregated signatures not having a verify method, this is stored converted to
/// a bare signature internally.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AggregateSignature(bls::Signature);
/// Type safety wrapper around a `blst` aggregate signature
#[derive(Clone, Debug)]
pub struct AggregateSignature(bls::AggregateSignature);

impl AggregateSignature {
/// Generates an aggregate signature from a list of signatures
pub fn aggregate<'a>(sigs: impl IntoIterator<Item = &'a Signature>) -> Result<Self, Error> {
let sigs: Vec<&bls::Signature> = sigs.into_iter().map(|s| &s.0).collect();
impl Default for AggregateSignature {
fn default() -> Self {
// This can't fail in production since we are decoding a known value.
brunoffranca marked this conversation as resolved.
Show resolved Hide resolved
Self::decode(&INFINITY_SIGNATURE).unwrap()
}
}

let aggregate = bls::AggregateSignature::aggregate(&sigs[..], true)
.map_err(Error::SignatureAggregation)?;
impl AggregateSignature {
/// Add a signature to the aggregation.
pub fn add(&mut self, sig: &Signature) {
// This cannot fail since we are not validating the signature.
self.0.add_signature(&sig.0, false).unwrap()
}

Ok(AggregateSignature(aggregate.to_signature()))
/// Generate a new aggregate signature from a list of signatures.
pub fn aggregate<'a>(sigs: impl IntoIterator<Item = &'a Signature>) -> Self {
let mut agg = Self::default();
for sig in sigs {
agg.add(sig);
}
agg
}

/// Verifies an aggregated signature for multiple messages against the provided list of public keys.
Expand All @@ -177,15 +200,15 @@ impl AggregateSignature {
pub fn verify<'a>(
&self,
msgs_and_pks: impl Iterator<Item = (&'a [u8], &'a PublicKey)>,
pompon0 marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<(), Error> {
) -> anyhow::Result<()> {
// Aggregate public keys if they are signing the same hash. Each public key aggregated
// is one fewer pairing to calculate.
let mut tree_map: BTreeMap<_, bls::AggregatePublicKey> = BTreeMap::new();

for (msg, pk) in msgs_and_pks {
if let Some(existing_pk) = tree_map.get_mut(msg) {
if let Err(err) = existing_pk.add_public_key(&pk.0, false) {
return Err(Error::AggregateSignatureVerification(err));
return Err(anyhow!("Error aggregating public keys: {err:?}"));
}
} else {
tree_map.insert(msg, bls::AggregatePublicKey::from_public_key(&pk.0));
Expand All @@ -200,26 +223,32 @@ impl AggregateSignature {
let public_keys: Vec<&bls::PublicKey> = public_keys.iter().collect();

// Verify the signature.
let result = self
.0
.aggregate_verify(true, &messages, DST, &public_keys, true);
// Due to the `blst` aggregated signatures not having a verify method, this is first converted to a bare signature.
let result =
self.0
.to_signature()
.aggregate_verify(true, &messages, DST, &public_keys, true);

match result {
BLST_ERROR::BLST_SUCCESS => Ok(()),
err => Err(Error::AggregateSignatureVerification(err)),
err => Err(anyhow!("Aggregate signature verification failure: {err:?}")),
}
}
}

impl ByteFmt for AggregateSignature {
/// This method also checks if the signature is in the correct subgroup.
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
let signature = bls::Signature::from_bytes(bytes)
let sig = bls::Signature::sig_validate(bytes, false)
.map_err(|err| anyhow!("Error decoding signature: {err:?}"))?;
Ok(AggregateSignature(signature))

Ok(AggregateSignature(bls::AggregateSignature::from_signature(
&sig,
)))
}

fn encode(&self) -> Vec<u8> {
self.0.to_bytes().to_vec()
self.0.to_signature().to_bytes().to_vec()
}
}

Expand All @@ -235,27 +264,36 @@ impl Ord for AggregateSignature {
}
}

impl Eq for AggregateSignature {}

impl PartialEq for AggregateSignature {
fn eq(&self, other: &Self) -> bool {
self.0.to_signature() == other.0.to_signature()
}
}

/// Type safety wrapper around a `blst` proof of possession.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProofOfPossession(bls::Signature);

impl ProofOfPossession {
/// Verifies a proof of possession against the provided public key
pub fn verify(&self, pk: &PublicKey) -> Result<(), Error> {
pub fn verify(&self, pk: &PublicKey) -> anyhow::Result<()> {
let msg = pk.encode();

let result = self.0.verify(true, &msg, DST_POP, &[], &pk.0, true);

match result {
BLST_ERROR::BLST_SUCCESS => Ok(()),
err => Err(Error::PopVerification(err)),
err => Err(anyhow!("Proof of possession verification failure: {err:?}")),
pompon0 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

impl ByteFmt for ProofOfPossession {
/// This method also checks if the signature is in the correct subgroup.
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
bls::Signature::from_bytes(bytes)
bls::Signature::sig_validate(bytes, false)
.map(Self)
.map_err(|err| anyhow!("Error decoding proof of possession: {err:?}"))
}
Expand All @@ -276,24 +314,3 @@ impl Ord for ProofOfPossession {
ByteFmt::encode(self).cmp(&ByteFmt::encode(other))
}
}

/// Error type for generating and interacting with BLS keys/signatures
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// Infinity public key.
#[error("Error infinity public key")]
InvalidInfinityPublicKey,
/// Error aggregating signatures
#[error("Error aggregating signatures: {0:?}")]
SignatureAggregation(BLST_ERROR),
/// Signature verification failure
#[error("Signature verification failure: {0:?}")]
SignatureVerification(BLST_ERROR),
/// Aggregate signature verification failure
#[error("Aggregate signature verification failure: {0:?}")]
AggregateSignatureVerification(BLST_ERROR),
/// Proof of possession verification failure
#[error("Proof of possession verification failure: {0:?}")]
PopVerification(BLST_ERROR),
}
2 changes: 1 addition & 1 deletion node/libs/crypto/src/bls12_381/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl Distribution<ProofOfPossession> for Standard {
impl Distribution<AggregateSignature> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> AggregateSignature {
let sig: Signature = self.sample(rng);
AggregateSignature(sig.0)
AggregateSignature(bls::AggregateSignature::from_signature(&sig.0))
}
}

Expand Down
15 changes: 11 additions & 4 deletions node/libs/crypto/src/bls12_381/tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use super::*;
use rand::{rngs::StdRng, Rng, SeedableRng};

// Represents the public key at infinity.
const INFINITY_PUBLIC_KEY: [u8; 96] = [
0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
];

#[test]
fn infinity_public_key_failure() {
PublicKey::decode(&INFINITY_PUBLIC_KEY)
.expect_err("Decoding the infinity public key should fail");
assert!(PublicKey::decode(&INFINITY_PUBLIC_KEY).is_err())
}

// Test signing and verifying a random message
Expand Down Expand Up @@ -68,7 +75,7 @@ fn aggregate_signature_smoke() {
let msg: [u8; 32] = rng.gen();

let sigs: Vec<Signature> = sks.iter().map(|k| k.sign(&msg)).collect();
let agg_sig = AggregateSignature::aggregate(&sigs).unwrap();
let agg_sig = AggregateSignature::aggregate(&sigs);

agg_sig.verify(pks.iter().map(|pk| (&msg[..], pk))).unwrap()
}
Expand All @@ -86,7 +93,7 @@ fn aggregate_signature_failure_smoke() {
// Take only three signatures for the aggregate
let sigs: Vec<Signature> = sks.iter().take(3).map(|k| k.sign(&msg)).collect();

let agg_sig = AggregateSignature::aggregate(&sigs).unwrap();
let agg_sig = AggregateSignature::aggregate(&sigs);

assert!(agg_sig.verify(pks.iter().map(|pk| (&msg[..], pk))).is_err())
}
9 changes: 9 additions & 0 deletions node/libs/crypto/src/bn254/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ impl AggregateSignature {
self.0.add_assign(&sig.0)
}

/// Generate a new aggregate signature from a list of signatures.
pub fn aggregate<'a>(sigs: impl IntoIterator<Item = &'a Signature>) -> Self {
let mut agg = Self::default();
for sig in sigs {
agg.add(sig);
}
agg
}

/// This function is intentionally non-generic and disallow inlining to ensure that compilation optimizations can be effectively applied.
/// This optimization is needed for ensuring that tests can run within a reasonable time frame.
#[inline(never)]
Expand Down
11 changes: 0 additions & 11 deletions node/libs/crypto/src/bn254/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,3 @@ impl Distribution<AggregateSignature> for Standard {
AggregateSignature(p)
}
}

impl AggregateSignature {
/// Generate a new aggregate signature from a list of signatures.
pub fn aggregate<'a>(sigs: impl IntoIterator<Item = &'a Signature>) -> Self {
let mut agg = Self::default();
for sig in sigs {
agg.add(sig);
}
agg
}
}
8 changes: 4 additions & 4 deletions node/libs/roles/src/validator/keys/aggregate_signature.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::{PublicKey, Signature};
use crate::validator::messages::{Msg, MsgHash};
use std::fmt;
use zksync_consensus_crypto::{bn254, ByteFmt, Text, TextFmt};
use zksync_consensus_crypto::{bls12_381, ByteFmt, Text, TextFmt};
use zksync_consensus_utils::enum_util::Variant;

/// An aggregate signature from a validator.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct AggregateSignature(pub(crate) bn254::AggregateSignature);
pub struct AggregateSignature(pub(crate) bls12_381::AggregateSignature);

impl AggregateSignature {
/// Add a signature to the aggregation.
Expand Down Expand Up @@ -51,14 +51,14 @@ impl ByteFmt for AggregateSignature {

impl TextFmt for AggregateSignature {
fn decode(text: Text) -> anyhow::Result<Self> {
text.strip("validator:aggregate_signature:bn254:")?
text.strip("validator:aggregate_signature:bls12_381:")?
.decode_hex()
.map(Self)
}

fn encode(&self) -> String {
format!(
"validator:aggregate_signature:bn254:{}",
"validator:aggregate_signature:bls12_381:{}",
hex::encode(ByteFmt::encode(&self.0))
)
}
Expand Down
Loading
Loading