Skip to content

Crypto verify benchmarks #801

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

Merged
merged 12 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
40 changes: 37 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ workflows:
test:
# Keep those job names in sync with .mergify.yml
jobs:
- package_crypto
- package_schema
- package_std
- package_storage
Expand All @@ -23,18 +24,20 @@ workflows:
requires:
- package_vm
- package_vm_cranelift
- package_crypto
filters:
branches:
only:
# Long living branches
- main
- /^[0-9]+\.[0-9]+$/
# 👇 Add your branch here if benchmarking matters to your work
# 👇Add your branch here if benchmarking matters to your work
- benchmarking
- update-wasmer
- metering-restart
- load-wasm-speed
- cache-analyze
- crypto-verify-benchmarks
deploy:
jobs:
- build_and_upload_devcontracts:
Expand All @@ -45,6 +48,33 @@ workflows:
ignore: /.*/

jobs:
package_crypto:
docker:
- image: rust:1.49.0
steps:
- checkout
- run:
name: Version information
command: rustc --version; cargo --version; rustup --version; rustup target list --installed
- restore_cache:
keys:
- cargocache-v2-package_crypto-rust:1.49.0-{{ checksum "Cargo.lock" }}
- run:
name: Build
working_directory: ~/project/packages/crypto
command: cargo build --locked
- run:
name: Run tests
working_directory: ~/project/packages/crypto
command: cargo test --locked
- save_cache:
paths:
- /usr/local/cargo/registry
- target/debug/.fingerprint
- target/debug/build
- target/debug/deps
key: cargocache-v2-package_crypto-rust:1.49.0-{{ checksum "Cargo.lock" }}

package_schema:
docker:
- image: rust:1.49.0
Expand Down Expand Up @@ -858,13 +888,17 @@ jobs:
keys:
- cargocache-v2-benchmarking-rust:1.49.0-{{ checksum "Cargo.lock" }}
- run:
name: Run benchmarks (Singlepass)
name: Run vm benchmarks (Singlepass)
working_directory: ~/project/packages/vm
command: cargo bench --no-default-features -- --color never --save-baseline singlepass
- run:
name: Run benchmarks (Cranelift)
name: Run vm benchmarks (Cranelift)
working_directory: ~/project/packages/vm
command: cargo bench --no-default-features --features cranelift -- --color never --save-baseline cranelift
- run:
name: Run crypto benchmarks
working_directory: ~/project/packages/crypto
command: cargo bench -- --color never --save-baseline crypto
- save_cache:
paths:
- /usr/local/cargo/registry
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ default = []
# This feature requires Rust nightly because it depends on the unstable backtrace feature.
backtraces = []

[lib]
# See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
bench = false

[dependencies]
k256 = { version = "0.7.2", features = ["ecdsa"] }
ed25519-zebra = "2"
Expand All @@ -22,10 +26,16 @@ rand_core = { version = "0.5", features = ["getrandom"] }
thiserror = "1.0"

[dev-dependencies]
criterion = "0.3"
serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
serde_json = "1.0"
sha2 = "0.9"
base64 = "0.13.0"
hex = "0.4"
hex-literal = "0.3.1"
english-numbers = "0.3"
elliptic-curve = "0.8.4"

[[bench]]
name = "main"
harness = false
7 changes: 7 additions & 0 deletions packages/crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates.
- `ed25519_batch_verify()`: Batch digital signature verification using the EdDSA
ed25519 scheme, for Tendemint signature / public key formats.

## Benchmarking

```
cd packages/crypto
cargo bench
```

## License

This package is part of the cosmwasm repository, licensed under the Apache
Expand Down
188 changes: 188 additions & 0 deletions packages/crypto/benches/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use criterion::{criterion_group, criterion_main, Criterion, PlottingBackend};
use std::time::Duration;

use english_numbers::convert_no_fmt;
use hex_literal::hex;
use serde::Deserialize;

// Crypto stuff
use digest::Digest;
use elliptic_curve::sec1::ToEncodedPoint;
use k256::ecdsa::SigningKey; // type alias
use sha2::Sha256;

use cosmwasm_crypto::{
ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify,
};
use std::cmp::min;

const COSMOS_SECP256K1_MSG_HEX: &str = "0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a02080112130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001";
const COSMOS_SECP256K1_SIGNATURE_HEX: &str = "c9dd20e07464d3a688ff4b710b1fbc027e495e797cfa0b4804da2ed117959227772de059808f765aa29b8f92edf30f4c2c5a438e30d3fe6897daa7141e3ce6f9";
const COSMOS_SECP256K1_PUBKEY_BASE64: &str = "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ";

// TEST 3 test vector from https://tools.ietf.org/html/rfc8032#section-7.1
const COSMOS_ED25519_MSG_HEX: &str = "af82";
const COSMOS_ED25519_SIGNATURE_HEX: &str = "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a";
const COSMOS_ED25519_PUBLIC_KEY_HEX: &str =
"fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025";

// Test data from https://tools.ietf.org/html/rfc8032#section-7.1
const COSMOS_ED25519_TESTS_JSON: &str = "./testdata/ed25519_tests.json";

#[derive(Deserialize, Debug)]
struct Encoded {
#[serde(rename = "privkey")]
private_key: String,
#[serde(rename = "pubkey")]
public_key: String,
message: String,
signature: String,
}

fn read_cosmos_sigs() -> Vec<Encoded> {
use std::fs::File;
use std::io::BufReader;

// Open the file in read-only mode with buffer.
let file = File::open(COSMOS_ED25519_TESTS_JSON).unwrap();
let reader = BufReader::new(file);

serde_json::from_reader(reader).unwrap()
}

fn read_decode_cosmos_sigs() -> (Vec<Vec<u8>>, Vec<Vec<u8>>, Vec<Vec<u8>>) {
let codes = read_cosmos_sigs();

let mut messages: Vec<Vec<u8>> = vec![];
let mut signatures: Vec<Vec<u8>> = vec![];
let mut public_keys: Vec<Vec<u8>> = vec![];

for encoded in codes {
let message = hex::decode(&encoded.message).unwrap();
messages.push(message);

let signature = hex::decode(&encoded.signature).unwrap();
signatures.push(signature);

let public_key = hex::decode(&encoded.public_key).unwrap();
public_keys.push(public_key);
}

(messages, signatures, public_keys)
}

fn bench_crypto(c: &mut Criterion) {
let mut group = c.benchmark_group("Crypto");

group.bench_function("secp256k1_verify", |b| {
let message = hex::decode(COSMOS_SECP256K1_MSG_HEX).unwrap();
let message_hash = Sha256::digest(&message);
let signature = hex::decode(COSMOS_SECP256K1_SIGNATURE_HEX).unwrap();
let public_key = base64::decode(COSMOS_SECP256K1_PUBKEY_BASE64).unwrap();
b.iter(|| {
assert!(secp256k1_verify(&message_hash, &signature, &public_key).unwrap());
});
});

group.bench_function("secp256k1_recover_pubkey", |b| {
let message_hash =
hex!("82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28");
let private_key =
hex!("3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1");
let r_s = hex!("99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66");
let recovery_param: u8 = 0;

let expected = SigningKey::from_bytes(&private_key)
.unwrap()
.verify_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();

b.iter(|| {
let pubkey = secp256k1_recover_pubkey(&message_hash, &r_s, recovery_param).unwrap();
assert_eq!(pubkey, expected);
});
});

group.bench_function("ed25519_verify", |b| {
let message = hex::decode(COSMOS_ED25519_MSG_HEX).unwrap();
let signature = hex::decode(COSMOS_ED25519_SIGNATURE_HEX).unwrap();
let public_key = hex::decode(COSMOS_ED25519_PUBLIC_KEY_HEX).unwrap();
b.iter(|| {
assert!(ed25519_verify(&message, &signature, &public_key).unwrap());
});
});

// Ed25519 batch verification of different batch lengths
{
let (messages, signatures, public_keys) = read_decode_cosmos_sigs();
let messages: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
let signatures: Vec<&[u8]> = signatures.iter().map(|m| m.as_slice()).collect();
let public_keys: Vec<&[u8]> = public_keys.iter().map(|m| m.as_slice()).collect();

for n in (1..=min(messages.len(), 10)).step_by(2) {
group.bench_function(
format!("ed25519_batch_verify_{}", convert_no_fmt(n as i64)),
|b| {
b.iter(|| {
assert!(ed25519_batch_verify(
&messages[..n],
&signatures[..n],
&public_keys[..n]
)
.unwrap());
});
},
);
}
}

// Ed25519 batch verification of different batch lengths, with the same pubkey
{
//FIXME: Use different messages / signatures
let messages = [hex::decode(COSMOS_ED25519_MSG_HEX).unwrap()];
let signatures = [hex::decode(COSMOS_ED25519_SIGNATURE_HEX).unwrap()];
let public_keys = [hex::decode(COSMOS_ED25519_PUBLIC_KEY_HEX).unwrap()];

let messages: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
let signatures: Vec<&[u8]> = signatures.iter().map(|m| m.as_slice()).collect();
let public_keys: Vec<&[u8]> = public_keys.iter().map(|m| m.as_slice()).collect();

for n in (1..10).step_by(2) {
group.bench_function(
format!(
"ed25519_batch_verify_one_pubkey_{}",
convert_no_fmt(n as i64)
),
|b| {
b.iter(|| {
assert!(ed25519_batch_verify(
&messages.repeat(n),
&signatures.repeat(n),
&public_keys
)
.unwrap());
});
},
);
}
}

group.finish();
}

fn make_config() -> Criterion {
Criterion::default()
.plotting_backend(PlottingBackend::Plotters)
.without_plots()
.measurement_time(Duration::new(10, 0))
.sample_size(12)
}

criterion_group!(
name = crypto;
config = make_config();
targets = bench_crypto
);
criterion_main!(crypto);
6 changes: 3 additions & 3 deletions packages/crypto/src/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ mod tests {
.unwrap());

// Wrong message fails
let bad_message_hash = Sha256::new().chain([MSG, "\0"].concat()).finalize();
let bad_message_hash = Sha256::new().chain(MSG).chain("\0").finalize();
assert!(!secp256k1_verify(
&bad_message_hash,
signature.as_bytes(),
Expand Down Expand Up @@ -245,7 +245,7 @@ mod tests {
let signature = hex::decode(sig).unwrap();

// Explicit hash
let message_hash = Sha256::new().chain(&message).finalize();
let message_hash = Sha256::digest(&message);

// secp256k1_verify works
assert!(
Expand Down Expand Up @@ -281,7 +281,7 @@ mod tests {
let message = hex::decode(&encoded.message).unwrap();

let hash = hex::decode(&encoded.message_hash).unwrap();
let message_hash = Sha256::new().chain(&message).finalize();
let message_hash = Sha256::digest(&message);
assert_eq!(hash.as_slice(), message_hash.as_slice());

let signature = hex::decode(&encoded.signature).unwrap();
Expand Down