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

Switch from bls12_381 to bn254 #11

Merged
merged 36 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5c7c527
Implement bn254
moshababo Oct 23, 2023
cae1291
Replace bls12_381
moshababo Oct 23, 2023
b35e202
[WIP] Use ark_bn254
moshababo Oct 27, 2023
972b7d6
Don't use multi pairing on non-aggregated verify
moshababo Oct 27, 2023
47b624f
Implement signatures aggregation
moshababo Oct 29, 2023
7071f2e
Merge branch 'main' into bn254
moshababo Oct 29, 2023
1cfbeb3
Post-merge fixes
moshababo Oct 29, 2023
e9f04e8
Downgrade multiple-versions "deny" to "warn"
moshababo Oct 29, 2023
cbb9737
cargo fmt
moshababo Oct 29, 2023
540c4f2
cargo cranky
moshababo Oct 29, 2023
a462c6a
Update node/Cargo.toml
moshababo Oct 30, 2023
59602fd
Skip hashbrown version discrepancy
moshababo Oct 30, 2023
09dc83e
Finalize removal of bn254 crate dep
moshababo Oct 30, 2023
8e70127
Update node/libs/crypto/src/bn254/hash.rs
moshababo Oct 30, 2023
6127656
Update node/libs/crypto/src/bn254/hash.rs
moshababo Oct 30, 2023
ba5823f
Derive PartialEq, Eq
moshababo Oct 30, 2023
d262d81
AggregateSignature to store G1 directly
moshababo Oct 30, 2023
2345e62
Generate random G1/G2 points directly
moshababo Oct 31, 2023
cf0a547
Undo bls12_381 mod removal
moshababo Oct 31, 2023
7f66642
Merge branch 'main' into bn254
moshababo Oct 31, 2023
d7c8bb2
Add benches
moshababo Oct 31, 2023
e23724f
opt=3 for crypto
pompon0 Oct 31, 2023
43f4dec
<4s
pompon0 Oct 31, 2023
efd701a
removed clock speedup
pompon0 Oct 31, 2023
69352b4
Merge branch 'main' into bn254
pompon0 Oct 31, 2023
71a1f76
Remove bls12_381 from protobuf schemas
moshababo Nov 1, 2023
96f1cba
Make wrapped scalar/group elements private
moshababo Nov 1, 2023
40faa1f
PublicKey to derive Hash
moshababo Nov 1, 2023
93999ce
Remove unused import
moshababo Nov 1, 2023
c352cc8
cargo fmt
moshababo Nov 2, 2023
8c3aa70
Increase range
moshababo Nov 2, 2023
e89656f
Remove instrumentation
moshababo Nov 2, 2023
91b4f64
Use anyhow::Context
moshababo Nov 2, 2023
28526fd
Add comment about non-generic no-inline functions
moshababo Nov 2, 2023
4959dd5
Add disclaimer regarding side-channel attacks
moshababo Nov 3, 2023
89646d6
Merge branch 'main' into bn254
moshababo Nov 6, 2023
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
293 changes: 270 additions & 23 deletions node/Cargo.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,21 @@ assert_matches = "1.5.0"
async-trait = "0.1.71"
bit-vec = "0.6"
blst = "0.3.10"
bn254 = "0.0.1"
moshababo marked this conversation as resolved.
Show resolved Hide resolved
ark-bn254 = "0.4.0"
ark-ec = "0.4.2"
num-traits = "0.2.17"
clap = { version = "4.3.3", features = ["derive"] }
ed25519-dalek = { version = "2.0.0", features = ["serde", "rand_core"] }
futures = "0.3.28"
hex = "0.4.3"
hyper = { version = "0.14.27", features = ["http1", "http2","server","tcp"] }
hyper = { version = "0.14.27", features = ["http1", "http2", "server", "tcp"] }
im = "15.1.0"
once_cell = "1.17.1"
pin-project = "1.1.0"
prost = "0.11.0"
prost-build = "0.11.0"
prost-reflect = { version = "0.11.0", features = ["derive","serde"] }
prost-reflect = { version = "0.11.0", features = ["derive", "serde"] }
prost-reflect-build = "0.11.0"
protoc-bin-vendored = "3.0.0"
prettyplease = "0.2.6"
Expand Down
4 changes: 2 additions & 2 deletions node/actors/consensus/src/leader/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ pub(crate) enum Error {
current_view: validator::ViewNumber,
},
#[error("received replica commit message with invalid signature")]
ReplicaCommitInvalidSignature(#[source] crypto::bls12_381::Error),
ReplicaCommitInvalidSignature(#[source] crypto::bn254::Error),
#[error("received replica prepare message with invalid signature")]
ReplicaPrepareInvalidSignature(#[source] crypto::bls12_381::Error),
ReplicaPrepareInvalidSignature(#[source] crypto::bn254::Error),
#[error("received replica prepare message with invalid high QC")]
ReplicaPrepareInvalidHighQC(#[source] anyhow::Error),
}
Expand Down
4 changes: 2 additions & 2 deletions node/actors/consensus/src/replica/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ pub(crate) enum Error {
current_phase: validator::Phase,
},
#[error("received leader commit message with invalid signature")]
LeaderCommitInvalidSignature(#[source] crypto::bls12_381::Error),
LeaderCommitInvalidSignature(#[source] crypto::bn254::Error),
#[error("received leader prepare message with invalid signature")]
LeaderPrepareInvalidSignature(#[source] crypto::bls12_381::Error),
LeaderPrepareInvalidSignature(#[source] crypto::bn254::Error),
#[error("received leader commit message with invalid justification")]
LeaderCommitInvalidJustification(#[source] anyhow::Error),
#[error("received leader prepare message with empty map in the justification")]
Expand Down
4 changes: 2 additions & 2 deletions node/actors/network/src/consensus/handshake/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{frame, noise};
use anyhow::Context as _;
use concurrency::{ctx, time};
use crypto::{bls12_381, ByteFmt};
use crypto::{bn254, ByteFmt};
use roles::{node, validator};
use schema::{proto::network::consensus as proto, read_required, ProtoFmt};

Expand Down Expand Up @@ -43,7 +43,7 @@ pub(super) enum Error {
#[error("unexpected peer")]
PeerMismatch,
#[error("validator signature {0}")]
Signature(#[from] bls12_381::Error),
Signature(#[from] bn254::Error),
#[error("stream {0}")]
Stream(#[source] anyhow::Error),
}
Expand Down
4 changes: 4 additions & 0 deletions node/libs/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
blst.workspace = true
bn254.workspace = true
ark-bn254.workspace = true
ark-ec.workspace = true
num-traits.workspace = true
ed25519-dalek.workspace = true
hex.workspace = true
rand.workspace = true
Expand Down
12 changes: 12 additions & 0 deletions node/libs/crypto/src/bn254/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Signature verification failure
#[error("Signature verification failure: {0:?}")]
SignatureVerification(bn254::Error),
/// Aggregate signature verification failure
#[error("Aggregate signature verification failure:")]
AggregateSignatureVerification,
/// Error aggregating signatures
#[error("Error aggregating signatures:")]
SignatureAggregation,
}
201 changes: 201 additions & 0 deletions node/libs/crypto/src/bn254/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! BLS signature scheme for BN254 curve.

use std::collections::BTreeMap;

use anyhow::anyhow;
use bn254::{ECDSA, PrivateKey};
use rand::Rng;

pub use crate::bn254::error::Error;
use crate::bn254_2::hash;
use crate::ByteFmt;

mod error;

mod testonly;
#[cfg(test)]
mod tests;

pub struct SecretKey(PrivateKey);

impl SecretKey {
/// Generates a random secret key
pub fn random<R: Rng>(rng: &mut R) -> Self {
let private_key = PrivateKey::random(rng);
return Self(private_key);
}
moshababo marked this conversation as resolved.
Show resolved Hide resolved

/// Produces a signature using this [`SecretKey`]
pub fn sign(&self, msg: &[u8]) -> Signature {
let sig = ECDSA::sign(&msg, &self.0).unwrap();
moshababo marked this conversation as resolved.
Show resolved Hide resolved
Signature(sig)
}

/// Gets the corresponding [`PublicKey`] for this [`SecretKey`]
pub fn public(&self) -> PublicKey {
let pk = bn254::PublicKey::from_private_key(&self.0);
PublicKey(pk)
}
}

impl ByteFmt for SecretKey {
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
bn254::PrivateKey::try_from(bytes)
.map(Self)
.map_err(|e| anyhow!("Failed to decode secret key: {e:?}"))
}

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

/// Type safety wrapper around a `bn254` public key.
#[derive(Clone)]
pub struct PublicKey(bn254::PublicKey);

impl PartialEq for PublicKey {
fn eq(&self, other: &Self) -> bool {
ByteFmt::encode(self).eq(&ByteFmt::encode(other))
}
}

impl Eq for PublicKey {}
moshababo marked this conversation as resolved.
Show resolved Hide resolved

impl ByteFmt for PublicKey {
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
bn254::PublicKey::from_compressed(bytes)
.map(Self)
.map_err(|err| anyhow!("Error decoding public key: {err:?}"))
}

fn encode(&self) -> Vec<u8> {
self.0.to_compressed().unwrap()
}
}

impl std::hash::Hash for PublicKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write(&self.0.to_compressed().unwrap())
}
}

impl PartialOrd for PublicKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for PublicKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
ByteFmt::encode(self).cmp(&ByteFmt::encode(other))
}
}

#[derive(Clone, Debug)]
pub struct Signature(bn254::Signature);

impl Signature {
pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> Result<(), Error> {
let result = ECDSA::verify(msg, &self.0, &pk.0);
match result {
Ok(()) => Ok(()),
Err(err) => Err(Error::SignatureVerification(err)),
}
}
}

impl PartialEq for Signature {
fn eq(&self, other: &Self) -> bool {
ByteFmt::encode(self).eq(&ByteFmt::encode(other))
}
}

impl Eq for Signature {}
moshababo marked this conversation as resolved.
Show resolved Hide resolved

impl ByteFmt for Signature {
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
bn254::Signature::from_compressed(bytes)
.map(Self)
.map_err(|err| anyhow!("Error decoding signature: {err:?}"))
}
fn encode(&self) -> Vec<u8> {
self.0.to_compressed().unwrap()
}
}

impl PartialOrd for Signature {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Signature {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
ByteFmt::encode(self).cmp(&ByteFmt::encode(other))
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AggregateSignature(Signature);
moshababo marked this conversation as resolved.
Show resolved Hide resolved

impl AggregateSignature {
pub fn aggregate<'a>(sigs: impl IntoIterator<Item = &'a Signature>) -> Self {
let sigs: Vec<bn254::Signature> = sigs.into_iter().map(|s| s.0).collect();
moshababo marked this conversation as resolved.
Show resolved Hide resolved
let mut agg = sigs[0];
moshababo marked this conversation as resolved.
Show resolved Hide resolved
for i in 1..sigs.len() {
agg = agg + sigs[i];
}

AggregateSignature(Signature(agg))
}

pub fn verify<'a>(
&self,
msgs_and_pks: impl Iterator<Item = (&'a [u8], &'a PublicKey)>,
) -> Result<(), Error> {
// 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<_, PublicKey> = BTreeMap::new();

for (msg, pk) in msgs_and_pks {
if let Some(existing_pk) = tree_map.get_mut(msg) {
let agg = PublicKey(existing_pk.0 + pk.0);
tree_map.insert(msg, agg);
} else {
tree_map.insert(msg, (*pk).clone());
}
}

for (msg, pk) in tree_map {
if let Err(err) = self.0.verify(msg, &pk) {
return Err(err);
}
}

Ok(())
}
}

impl ByteFmt for AggregateSignature {
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
let signature = Signature::decode(bytes)?;
Ok(AggregateSignature(signature))
}

fn encode(&self) -> Vec<u8> {
Signature::encode(&self.0)
}
}

impl PartialOrd for AggregateSignature {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Off-topic(?): Is PartialOrd / Ord required only for BTreeMaps in PrepareQC? Could HashMap be used there? It's not quite obvious why signatures should be ordered semantically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is PartialOrd / Ord required only for BTreeMaps in PrepareQC?

Yes.

Could HashMap be used there? It's not quite obvious why signatures should be ordered semantically.

I didn't find use case for the current type of ordering. @pompon0 @brunoffranca any thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need reserialization of signable messages (like PrepareQC) to be an identity, so that signatures are valid at all times. This is kind of constraining, but the alternative would be to pass around the serialized message at all times so that reserialization is never needed (which is in fact more bullet-proof, but a bit costly and makes message handling more complex).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for AggregateSignature {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
ByteFmt::encode(self).cmp(&ByteFmt::encode(other))
}
}
35 changes: 35 additions & 0 deletions node/libs/crypto/src/bn254/testonly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Random key generation, intended for use in testing

use super::{AggregateSignature, ByteFmt, PublicKey, SecretKey, Signature};
use rand::{distributions::Standard, prelude::Distribution, Rng};

/// Generates a random SecretKey. This is meant for testing purposes.
impl Distribution<SecretKey> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> SecretKey {
SecretKey::decode(&rng.gen::<[u8; 32]>()).unwrap()
}
}

/// Generates a random Signature. This is meant for testing purposes.
impl Distribution<Signature> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Signature {
let key = rng.gen::<SecretKey>();
let msg = rng.gen::<[u8; 4]>();
key.sign(&msg)
moshababo marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Generates a random AggregateSignature. This is meant for testing purposes.
impl Distribution<AggregateSignature> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> AggregateSignature {
let sig: Signature = self.sample(rng);
AggregateSignature(sig)
}
}

/// Generates a random PublicKey. This is meant for testing purposes.
impl Distribution<PublicKey> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PublicKey {
rng.gen::<SecretKey>().public()
}
}
Loading