Skip to content

Commit a674bf7

Browse files
authored
Merge pull request #801 from CosmWasm/crypto-verify-benchmarks
Crypto verify benchmarks
2 parents 29b791a + 6f855ed commit a674bf7

File tree

6 files changed

+253
-6
lines changed

6 files changed

+253
-6
lines changed

.circleci/config.yml

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ workflows:
44
test:
55
# Keep those job names in sync with .mergify.yml
66
jobs:
7+
- package_crypto
78
- package_schema
89
- package_std
910
- package_storage
@@ -23,18 +24,20 @@ workflows:
2324
requires:
2425
- package_vm
2526
- package_vm_cranelift
27+
- package_crypto
2628
filters:
2729
branches:
2830
only:
2931
# Long living branches
3032
- main
3133
- /^[0-9]+\.[0-9]+$/
32-
# 👇 Add your branch here if benchmarking matters to your work
34+
# 👇Add your branch here if benchmarking matters to your work
3335
- benchmarking
3436
- update-wasmer
3537
- metering-restart
3638
- load-wasm-speed
3739
- cache-analyze
40+
- crypto-verify-benchmarks
3841
deploy:
3942
jobs:
4043
- build_and_upload_devcontracts:
@@ -45,6 +48,33 @@ workflows:
4548
ignore: /.*/
4649

4750
jobs:
51+
package_crypto:
52+
docker:
53+
- image: rust:1.49.0
54+
steps:
55+
- checkout
56+
- run:
57+
name: Version information
58+
command: rustc --version; cargo --version; rustup --version; rustup target list --installed
59+
- restore_cache:
60+
keys:
61+
- cargocache-v2-package_crypto-rust:1.49.0-{{ checksum "Cargo.lock" }}
62+
- run:
63+
name: Build
64+
working_directory: ~/project/packages/crypto
65+
command: cargo build --locked
66+
- run:
67+
name: Run tests
68+
working_directory: ~/project/packages/crypto
69+
command: cargo test --locked
70+
- save_cache:
71+
paths:
72+
- /usr/local/cargo/registry
73+
- target/debug/.fingerprint
74+
- target/debug/build
75+
- target/debug/deps
76+
key: cargocache-v2-package_crypto-rust:1.49.0-{{ checksum "Cargo.lock" }}
77+
4878
package_schema:
4979
docker:
5080
- image: rust:1.49.0
@@ -858,13 +888,17 @@ jobs:
858888
keys:
859889
- cargocache-v2-benchmarking-rust:1.49.0-{{ checksum "Cargo.lock" }}
860890
- run:
861-
name: Run benchmarks (Singlepass)
891+
name: Run vm benchmarks (Singlepass)
862892
working_directory: ~/project/packages/vm
863893
command: cargo bench --no-default-features -- --color never --save-baseline singlepass
864894
- run:
865-
name: Run benchmarks (Cranelift)
895+
name: Run vm benchmarks (Cranelift)
866896
working_directory: ~/project/packages/vm
867897
command: cargo bench --no-default-features --features cranelift -- --color never --save-baseline cranelift
898+
- run:
899+
name: Run crypto benchmarks
900+
working_directory: ~/project/packages/crypto
901+
command: cargo bench -- --color never --save-baseline crypto
868902
- save_cache:
869903
paths:
870904
- /usr/local/cargo/registry

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/crypto/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ default = []
1414
# This feature requires Rust nightly because it depends on the unstable backtrace feature.
1515
backtraces = []
1616

17+
[lib]
18+
# See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
19+
bench = false
20+
1721
[dependencies]
1822
k256 = { version = "0.7.2", features = ["ecdsa"] }
1923
ed25519-zebra = "2"
@@ -22,10 +26,16 @@ rand_core = { version = "0.5", features = ["getrandom"] }
2226
thiserror = "1.0"
2327

2428
[dev-dependencies]
29+
criterion = "0.3"
2530
serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
2631
serde_json = "1.0"
2732
sha2 = "0.9"
2833
base64 = "0.13.0"
2934
hex = "0.4"
3035
hex-literal = "0.3.1"
36+
english-numbers = "0.3"
3137
elliptic-curve = "0.8.4"
38+
39+
[[bench]]
40+
name = "main"
41+
harness = false

packages/crypto/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates.
1515
- `ed25519_batch_verify()`: Batch digital signature verification using the EdDSA
1616
ed25519 scheme, for Tendemint signature / public key formats.
1717

18+
## Benchmarking
19+
20+
```
21+
cd packages/crypto
22+
cargo bench
23+
```
24+
1825
## License
1926

2027
This package is part of the cosmwasm repository, licensed under the Apache

packages/crypto/benches/main.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use criterion::{criterion_group, criterion_main, Criterion, PlottingBackend};
2+
use std::time::Duration;
3+
4+
use english_numbers::convert_no_fmt;
5+
use hex_literal::hex;
6+
use serde::Deserialize;
7+
8+
// Crypto stuff
9+
use digest::Digest;
10+
use elliptic_curve::sec1::ToEncodedPoint;
11+
use k256::ecdsa::SigningKey; // type alias
12+
use sha2::Sha256;
13+
14+
use cosmwasm_crypto::{
15+
ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify,
16+
};
17+
use std::cmp::min;
18+
19+
const COSMOS_SECP256K1_MSG_HEX: &str = "0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a02080112130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001";
20+
const COSMOS_SECP256K1_SIGNATURE_HEX: &str = "c9dd20e07464d3a688ff4b710b1fbc027e495e797cfa0b4804da2ed117959227772de059808f765aa29b8f92edf30f4c2c5a438e30d3fe6897daa7141e3ce6f9";
21+
const COSMOS_SECP256K1_PUBKEY_BASE64: &str = "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ";
22+
23+
// TEST 3 test vector from https://tools.ietf.org/html/rfc8032#section-7.1
24+
const COSMOS_ED25519_MSG_HEX: &str = "af82";
25+
const COSMOS_ED25519_SIGNATURE_HEX: &str = "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a";
26+
const COSMOS_ED25519_PUBLIC_KEY_HEX: &str =
27+
"fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025";
28+
29+
// Test data from https://tools.ietf.org/html/rfc8032#section-7.1
30+
const COSMOS_ED25519_TESTS_JSON: &str = "./testdata/ed25519_tests.json";
31+
32+
#[derive(Deserialize, Debug)]
33+
struct Encoded {
34+
#[serde(rename = "privkey")]
35+
private_key: String,
36+
#[serde(rename = "pubkey")]
37+
public_key: String,
38+
message: String,
39+
signature: String,
40+
}
41+
42+
fn read_cosmos_sigs() -> Vec<Encoded> {
43+
use std::fs::File;
44+
use std::io::BufReader;
45+
46+
// Open the file in read-only mode with buffer.
47+
let file = File::open(COSMOS_ED25519_TESTS_JSON).unwrap();
48+
let reader = BufReader::new(file);
49+
50+
serde_json::from_reader(reader).unwrap()
51+
}
52+
53+
fn read_decode_cosmos_sigs() -> (Vec<Vec<u8>>, Vec<Vec<u8>>, Vec<Vec<u8>>) {
54+
let codes = read_cosmos_sigs();
55+
56+
let mut messages: Vec<Vec<u8>> = vec![];
57+
let mut signatures: Vec<Vec<u8>> = vec![];
58+
let mut public_keys: Vec<Vec<u8>> = vec![];
59+
60+
for encoded in codes {
61+
let message = hex::decode(&encoded.message).unwrap();
62+
messages.push(message);
63+
64+
let signature = hex::decode(&encoded.signature).unwrap();
65+
signatures.push(signature);
66+
67+
let public_key = hex::decode(&encoded.public_key).unwrap();
68+
public_keys.push(public_key);
69+
}
70+
71+
(messages, signatures, public_keys)
72+
}
73+
74+
fn bench_crypto(c: &mut Criterion) {
75+
let mut group = c.benchmark_group("Crypto");
76+
77+
group.bench_function("secp256k1_verify", |b| {
78+
let message = hex::decode(COSMOS_SECP256K1_MSG_HEX).unwrap();
79+
let message_hash = Sha256::digest(&message);
80+
let signature = hex::decode(COSMOS_SECP256K1_SIGNATURE_HEX).unwrap();
81+
let public_key = base64::decode(COSMOS_SECP256K1_PUBKEY_BASE64).unwrap();
82+
b.iter(|| {
83+
assert!(secp256k1_verify(&message_hash, &signature, &public_key).unwrap());
84+
});
85+
});
86+
87+
group.bench_function("secp256k1_recover_pubkey", |b| {
88+
let message_hash =
89+
hex!("82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28");
90+
let private_key =
91+
hex!("3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1");
92+
let r_s = hex!("99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66");
93+
let recovery_param: u8 = 0;
94+
95+
let expected = SigningKey::from_bytes(&private_key)
96+
.unwrap()
97+
.verify_key()
98+
.to_encoded_point(false)
99+
.as_bytes()
100+
.to_vec();
101+
102+
b.iter(|| {
103+
let pubkey = secp256k1_recover_pubkey(&message_hash, &r_s, recovery_param).unwrap();
104+
assert_eq!(pubkey, expected);
105+
});
106+
});
107+
108+
group.bench_function("ed25519_verify", |b| {
109+
let message = hex::decode(COSMOS_ED25519_MSG_HEX).unwrap();
110+
let signature = hex::decode(COSMOS_ED25519_SIGNATURE_HEX).unwrap();
111+
let public_key = hex::decode(COSMOS_ED25519_PUBLIC_KEY_HEX).unwrap();
112+
b.iter(|| {
113+
assert!(ed25519_verify(&message, &signature, &public_key).unwrap());
114+
});
115+
});
116+
117+
// Ed25519 batch verification of different batch lengths
118+
{
119+
let (messages, signatures, public_keys) = read_decode_cosmos_sigs();
120+
let messages: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
121+
let signatures: Vec<&[u8]> = signatures.iter().map(|m| m.as_slice()).collect();
122+
let public_keys: Vec<&[u8]> = public_keys.iter().map(|m| m.as_slice()).collect();
123+
124+
for n in (1..=min(messages.len(), 10)).step_by(2) {
125+
group.bench_function(
126+
format!("ed25519_batch_verify_{}", convert_no_fmt(n as i64)),
127+
|b| {
128+
b.iter(|| {
129+
assert!(ed25519_batch_verify(
130+
&messages[..n],
131+
&signatures[..n],
132+
&public_keys[..n]
133+
)
134+
.unwrap());
135+
});
136+
},
137+
);
138+
}
139+
}
140+
141+
// Ed25519 batch verification of different batch lengths, with the same pubkey
142+
{
143+
//FIXME: Use different messages / signatures
144+
let messages = [hex::decode(COSMOS_ED25519_MSG_HEX).unwrap()];
145+
let signatures = [hex::decode(COSMOS_ED25519_SIGNATURE_HEX).unwrap()];
146+
let public_keys = [hex::decode(COSMOS_ED25519_PUBLIC_KEY_HEX).unwrap()];
147+
148+
let messages: Vec<&[u8]> = messages.iter().map(|m| m.as_slice()).collect();
149+
let signatures: Vec<&[u8]> = signatures.iter().map(|m| m.as_slice()).collect();
150+
let public_keys: Vec<&[u8]> = public_keys.iter().map(|m| m.as_slice()).collect();
151+
152+
for n in (1..10).step_by(2) {
153+
group.bench_function(
154+
format!(
155+
"ed25519_batch_verify_one_pubkey_{}",
156+
convert_no_fmt(n as i64)
157+
),
158+
|b| {
159+
b.iter(|| {
160+
assert!(ed25519_batch_verify(
161+
&messages.repeat(n),
162+
&signatures.repeat(n),
163+
&public_keys
164+
)
165+
.unwrap());
166+
});
167+
},
168+
);
169+
}
170+
}
171+
172+
group.finish();
173+
}
174+
175+
fn make_config() -> Criterion {
176+
Criterion::default()
177+
.plotting_backend(PlottingBackend::Plotters)
178+
.without_plots()
179+
.measurement_time(Duration::new(10, 0))
180+
.sample_size(12)
181+
}
182+
183+
criterion_group!(
184+
name = crypto;
185+
config = make_config();
186+
targets = bench_crypto
187+
);
188+
criterion_main!(crypto);

packages/crypto/src/secp256k1.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ mod tests {
206206
.unwrap());
207207

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

247247
// Explicit hash
248-
let message_hash = Sha256::new().chain(&message).finalize();
248+
let message_hash = Sha256::digest(&message);
249249

250250
// secp256k1_verify works
251251
assert!(
@@ -281,7 +281,7 @@ mod tests {
281281
let message = hex::decode(&encoded.message).unwrap();
282282

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

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

0 commit comments

Comments
 (0)