Skip to content

Commit

Permalink
feat: Parse and expand authority public keys once and reuse (#121)
Browse files Browse the repository at this point in the history
* Parse and expand authority public keys once and reuse
* Key derivation in single place + add TODO validation issue

Co-authored-by: George Danezis <[email protected]>
  • Loading branch information
gdanezis and George Danezis authored Jan 5, 2022
1 parent e29c107 commit 5a482af
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 38 deletions.
5 changes: 1 addition & 4 deletions fastpay/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,7 @@ impl ClientServerBenchmark {
for _ in 0..self.committee_size {
keys.push(get_key_pair());
}
let committee = Committee {
voting_rights: keys.iter().map(|(k, _)| (*k, 1)).collect(),
total_votes: self.committee_size,
};
let committee = Committee::new(keys.iter().map(|(k, _)| (*k, 1)).collect());

// Pick an authority and create state.
let (public_auth0, secret_auth0) = keys.pop().unwrap();
Expand Down
5 changes: 1 addition & 4 deletions fastpay/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,7 @@ fn make_benchmark_certificates_from_orders_and_server_configs(
let server_config = AuthorityServerConfig::read(file).expect("Fail to read server config");
keys.push((server_config.authority.address, server_config.key));
}
let committee = Committee {
voting_rights: keys.iter().map(|(k, _)| (*k, 1)).collect(),
total_votes: keys.len(),
};
let committee = Committee::new(keys.iter().map(|(k, _)| (*k, 1)).collect());
assert!(
keys.len() >= committee.quorum_threshold(),
"Not enough server configs were provided with --server-configs"
Expand Down
54 changes: 28 additions & 26 deletions fastx_types/src/base_types.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// SPDX-License-Identifier: Apache-2.0
use crate::error::FastPayError;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};

use ed25519_dalek as dalek;
use ed25519_dalek::{Digest, Signer, Verifier};
use ed25519_dalek::{Digest, PublicKey, Signer, Verifier};
use move_core_types::account_address::AccountAddress;
use move_core_types::ident_str;
use move_core_types::identifier::IdentStr;
Expand Down Expand Up @@ -44,6 +45,18 @@ impl PublicKeyBytes {
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}

pub fn to_public_key(&self) -> Result<PublicKey, FastPayError> {
// TODO(https://github.com/MystenLabs/fastnft/issues/101): Do better key validation
// to ensure the bytes represent a point on the curve.
PublicKey::from_bytes(self.as_ref()).map_err(|_| FastPayError::InvalidAuthenticator)
}
}

impl AsRef<[u8]> for PublicKeyBytes {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}

// TODO(https://github.com/MystenLabs/fastnft/issues/101): more robust key validation
Expand Down Expand Up @@ -425,31 +438,25 @@ impl Signature {
Signature(signature)
}

fn check_internal<T>(
&self,
value: &T,
author: FastPayAddress,
) -> Result<(), dalek::SignatureError>
pub fn check<T>(&self, value: &T, author: FastPayAddress) -> Result<(), FastPayError>
where
T: Signable<Vec<u8>>,
{
let mut message = Vec::new();
value.write(&mut message);
let public_key = dalek::PublicKey::from_bytes(&author.0)?;
public_key.verify(&message, &self.0)
}

pub fn check<T>(&self, value: &T, author: FastPayAddress) -> Result<(), FastPayError>
where
T: Signable<Vec<u8>>,
{
self.check_internal(value, author)
let public_key = author.to_public_key()?;
public_key
.verify(&message, &self.0)
.map_err(|error| FastPayError::InvalidSignature {
error: format!("{}", error),
})
}

fn verify_batch_internal<'a, T, I>(value: &'a T, votes: I) -> Result<(), dalek::SignatureError>
pub fn verify_batch<'a, T, I>(
value: &'a T,
votes: I,
key_cache: &HashMap<PublicKeyBytes, PublicKey>,
) -> Result<(), FastPayError>
where
T: Signable<Vec<u8>>,
I: IntoIterator<Item = &'a (FastPayAddress, Signature)>,
Expand All @@ -462,17 +469,12 @@ impl Signature {
for (addr, sig) in votes.into_iter() {
messages.push(&msg);
signatures.push(sig.0);
public_keys.push(dalek::PublicKey::from_bytes(&addr.0)?);
match key_cache.get(addr) {
Some(v) => public_keys.push(*v),
None => public_keys.push(addr.to_public_key()?),
}
}
dalek::verify_batch(&messages[..], &signatures[..], &public_keys[..])
}

pub fn verify_batch<'a, T, I>(value: &'a T, votes: I) -> Result<(), FastPayError>
where
T: Signable<Vec<u8>>,
I: IntoIterator<Item = &'a (FastPayAddress, Signature)>,
{
Signature::verify_batch_internal(value, votes).map_err(|error| {
dalek::verify_batch(&messages[..], &signatures[..], &public_keys[..]).map_err(|error| {
FastPayError::InvalidSignature {
error: format!("{}", error),
}
Expand Down
13 changes: 10 additions & 3 deletions fastx_types/src/committee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@
// SPDX-License-Identifier: Apache-2.0

use super::base_types::*;
use std::collections::BTreeMap;
use ed25519_dalek::PublicKey;
use std::collections::{BTreeMap, HashMap};

#[derive(Eq, PartialEq, Clone, Hash, Debug)]
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Committee {
pub voting_rights: BTreeMap<AuthorityName, usize>,
pub total_votes: usize,
pub expanded_keys: HashMap<AuthorityName, PublicKey>,
}

impl Committee {
pub fn new(voting_rights: BTreeMap<AuthorityName, usize>) -> Self {
let total_votes = voting_rights.iter().fold(0, |sum, (_, votes)| sum + *votes);
let total_votes = voting_rights.iter().map(|(_, votes)| votes).sum();
let expanded_keys: HashMap<_, _> = voting_rights
.iter()
.map(|(addr, _)| (*addr, addr.to_public_key().expect("Invalid Authority Key")))
.collect();
Committee {
voting_rights,
total_votes,
expanded_keys,
}
}

Expand Down
1 change: 1 addition & 0 deletions fastx_types/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ impl CertifiedOrder {
Signature::verify_batch(
&self.order.kind,
std::iter::once(&inner_sig).chain(&self.signatures),
&committee.expanded_keys,
)
}
}
Expand Down
9 changes: 8 additions & 1 deletion fastx_types/src/unit_tests/serialize_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,17 @@ fn test_time_cert() {
signatures: Vec::new(),
};

use ed25519_dalek::PublicKey;
use std::collections::HashMap;
let mut cache = HashMap::new();
for _ in 0..7 {
let (authority_name, authority_key) = get_key_pair();
let sig = Signature::new(&cert.order.kind, &authority_key);
cert.signatures.push((authority_name, sig));
cache.insert(
authority_name,
PublicKey::from_bytes(authority_name.as_ref()).expect("No problem parsing key."),
);
}

let mut buf = Vec::new();
Expand All @@ -330,7 +337,7 @@ fn test_time_cert() {
let mut buf2 = buf.as_slice();
for _ in 0..count {
if let SerializedMessage::Cert(cert) = deserialize_message(&mut buf2).unwrap() {
Signature::verify_batch(&cert.order.kind, &cert.signatures).unwrap();
Signature::verify_batch(&cert.order.kind, &cert.signatures, &cache).unwrap();
}
}
assert!(deserialize_message(buf2).is_err());
Expand Down

1 comment on commit 5a482af

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

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

Bench results

�[0m�[1m�[33mwarning�[0m�[0m�[1m: unused variable: num_immutable_objects�[0m
�[0m �[0m�[0m�[1m�[38;5;12m--> �[0m�[0mfastx_programmability/adapter/src/adapter.rs:370:13�[0m
�[0m �[0m�[0m�[1m�[38;5;12m|�[0m
�[0m�[1m�[38;5;12m370�[0m�[0m �[0m�[0m�[1m�[38;5;12m| �[0m�[0m let mut num_immutable_objects = 0;�[0m
�[0m �[0m�[0m�[1m�[38;5;12m| �[0m�[0m �[0m�[0m�[1m�[33m^^^^^^^^^^^^^^^^^^^^^�[0m�[0m �[0m�[0m�[1m�[33mhelp: if this is intentional, prefix it with an underscore: _num_immutable_objects�[0m
�[0m �[0m�[0m�[1m�[38;5;12m= �[0m�[0m�[1mnote�[0m�[0m: #[warn(unused_variables)] on by default�[0m

�[0m�[0m�[1m�[33mwarning�[0m�[1m:�[0m fastx-adapter (lib) generated 1 warning
�[0m�[0m�[1m�[32m Finished�[0m release [optimized + debuginfo] target(s) in 3.62s
�[0m�[0m�[1m�[32m Running�[0m target/release/bench
[2022-01-05T15:56:19Z INFO bench] Starting benchmark: OrdersAndCerts
[2022-01-05T15:56:19Z INFO bench] Preparing accounts.
[2022-01-05T15:56:21Z INFO bench] Preparing transactions.
[2022-01-05T15:56:29Z INFO fastpay::network] Listening to Tcp traffic on 127.0.0.1:9555
[2022-01-05T15:56:30Z INFO bench] Set max_in_flight to 500
[2022-01-05T15:56:30Z INFO bench] Sending requests.
[2022-01-05T15:56:30Z INFO fastpay::network] Sending Tcp requests to 127.0.0.1:9555
[2022-01-05T15:56:30Z INFO fastpay::network] 127.0.0.1:9555 has processed 5000 packets
[2022-01-05T15:56:31Z INFO fastpay::network] In flight 500 Remaining 35000
[2022-01-05T15:56:31Z INFO fastpay::network] 127.0.0.1:9555 has processed 10000 packets
[2022-01-05T15:56:32Z INFO fastpay::network] 127.0.0.1:9555 has processed 15000 packets
[2022-01-05T15:56:33Z INFO fastpay::network] In flight 500 Remaining 30000
[2022-01-05T15:56:33Z INFO fastpay::network] 127.0.0.1:9555 has processed 20000 packets
[2022-01-05T15:56:34Z INFO fastpay::network] 127.0.0.1:9555 has processed 25000 packets
[2022-01-05T15:56:34Z INFO fastpay::network] In flight 500 Remaining 25000
[2022-01-05T15:56:35Z INFO fastpay::network] 127.0.0.1:9555 has processed 30000 packets
[2022-01-05T15:56:35Z INFO fastpay::network] In flight 500 Remaining 25000
[2022-01-05T15:56:35Z INFO fastpay::network] 127.0.0.1:9555 has processed 35000 packets
[2022-01-05T15:56:36Z INFO fastpay::network] In flight 500 Remaining 20000
[2022-01-05T15:56:36Z INFO fastpay::network] 127.0.0.1:9555 has processed 40000 packets
[2022-01-05T15:56:37Z INFO fastpay::network] 127.0.0.1:9555 has processed 45000 packets
[2022-01-05T15:56:38Z INFO fastpay::network] In flight 500 Remaining 15000
[2022-01-05T15:56:38Z INFO fastpay::network] 127.0.0.1:9555 has processed 50000 packets
[2022-01-05T15:56:39Z INFO fastpay::network] 127.0.0.1:9555 has processed 55000 packets
[2022-01-05T15:56:39Z INFO fastpay::network] In flight 500 Remaining 10000
[2022-01-05T15:56:40Z INFO fastpay::network] 127.0.0.1:9555 has processed 60000 packets
[2022-01-05T15:56:40Z INFO fastpay::network] In flight 500 Remaining 10000
[2022-01-05T15:56:40Z INFO fastpay::network] 127.0.0.1:9555 has processed 65000 packets
[2022-01-05T15:56:41Z INFO fastpay::network] In flight 500 Remaining 5000
[2022-01-05T15:56:41Z INFO fastpay::network] 127.0.0.1:9555 has processed 70000 packets
[2022-01-05T15:56:42Z INFO fastpay::network] 127.0.0.1:9555 has processed 75000 packets
[2022-01-05T15:56:42Z INFO fastpay::network] Done sending Tcp requests to 127.0.0.1:9555
[2022-01-05T15:56:43Z INFO fastpay::network] 127.0.0.1:9555 has processed 80000 packets
[2022-01-05T15:56:43Z INFO fastpay::network] Done sending Tcp requests to 127.0.0.1:9555
[2022-01-05T15:56:43Z INFO bench] Received 80000 responses.
[2022-01-05T15:56:43Z WARN bench] Completed benchmark for OrdersAndCerts
Total time: 13126270us, items: 40000, tx/sec: 3047.3241827266997

Please sign in to comment.