Skip to content

Commit 5f8e579

Browse files
committed
fixup gas charging and add benchmarks
It turns out our numbers mostly don't change (except that our hashing times are now in-line with normal sha256 times...).
1 parent 8231704 commit 5f8e579

File tree

4 files changed

+106
-115
lines changed

4 files changed

+106
-115
lines changed

fvm/src/gas/price_list.rs

+16-37
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::ops::Mul;
77

88
use anyhow::Context;
99
use fvm_shared::clock::ChainEpoch;
10-
use fvm_shared::crypto::signature::SignatureType;
1110
use fvm_shared::piece::PieceInfo;
1211
use fvm_shared::sector::{
1312
AggregateSealVerifyProofAndInfos, RegisteredPoStProof, RegisteredSealProof, ReplicaUpdateInfo,
@@ -102,19 +101,14 @@ lazy_static! {
102101
address_lookup: Gas::new(1_050_000),
103102
address_assignment: Gas::new(1_000_000),
104103

105-
sig_cost: total_enum_map!{
106-
SignatureType {
107-
Secp256k1 => ScalingCost {
108-
flat: Gas::new(1637292),
109-
scale: Gas::new(10),
110-
},
111-
BLS => ScalingCost{
112-
flat: Gas::new(16598605),
113-
scale: Gas::new(26),
114-
},
115-
}
116-
},
117104
secp256k1_recover_cost: Gas::new(1637292),
105+
bls_pairing_cost: Gas::new(8299302),
106+
bls_hashing_cost: ScalingCost {
107+
flat: Gas::zero(),
108+
// TODO: Can we just use the "sha256" number below?
109+
scale: Gas::new(7),
110+
},
111+
118112
hashing_cost: total_enum_map! {
119113
SupportedHashes {
120114
Sha2_256 => ScalingCost {
@@ -398,12 +392,12 @@ pub struct PriceList {
398392
/// Storage gas cost for adding a new actor to the state tree.
399393
pub(crate) actor_create_storage: Gas,
400394

401-
/// Gas cost for verifying a cryptographic signature.
402-
pub(crate) sig_cost: HashMap<SignatureType, ScalingCost>,
403-
404395
/// Gas cost for recovering secp256k1 signer public key
405396
pub(crate) secp256k1_recover_cost: Gas,
406397

398+
pub(crate) bls_pairing_cost: Gas,
399+
pub(crate) bls_hashing_cost: ScalingCost,
400+
407401
pub(crate) hashing_cost: HashMap<SupportedHashes, ScalingCost>,
408402

409403
/// Gas cost for walking up the chain.
@@ -577,39 +571,24 @@ impl PriceList {
577571
GasCharge::new("OnDeleteActor", Zero::zero(), Zero::zero())
578572
}
579573

580-
/// Returns gas required for signature verification.
581-
#[inline]
582-
pub fn on_verify_signature(&self, sig_type: SignatureType, data_len: usize) -> GasCharge {
583-
let cost = self.sig_cost[&sig_type];
584-
let gas = cost.apply(data_len);
585-
GasCharge::new("OnVerifySignature", gas, Zero::zero())
586-
}
587-
588574
/// Returns gas required for BLS aggregate signature verification.
575+
#[inline]
589576
pub fn on_verify_aggregate_signature(&self, num_sigs: usize, data_len: usize) -> GasCharge {
590-
// TODO: the gas cost calculated below is only an estimate; its actual value is yet to be
591-
// benchmarked.
592-
593577
// When `num_sigs` BLS signatures are aggregated into a single signature, the aggregate
594578
// signature verifier must perform `num_sigs + 1` expensive pairing operations (one
595579
// pairing on the aggregate signature, and one pairing for each signed plaintext's digest).
596580
//
597581
// Note that `bls_signatures` rearranges the textbook verifier equation (containing
598582
// `num_sigs + 1` full pairings) into a more efficient equation containing `num_sigs + 1`
599583
// Miller loops and one final exponentiation.
600-
let (num_pairings, data_len) = (num_sigs as u64 + 1, data_len as u64);
584+
let num_pairings = num_sigs as u64 + 1;
601585

602-
let ScalingCost {
603-
flat: gas_two_pairings,
604-
scale: gas_per_byte,
605-
} = self.sig_cost[&SignatureType::BLS];
606-
let gas_one_pairing = gas_two_pairings.as_milligas() / 2;
607-
let gas_pairings = num_pairings * gas_one_pairing;
608-
let gas_hashing = data_len * gas_per_byte.as_milligas();
586+
let gas_pairings = self.bls_pairing_cost * num_pairings;
587+
let gas_hashing = self.bls_hashing_cost.apply(data_len);
609588

610589
GasCharge::new(
611-
"OnVerifySignature",
612-
Gas::from_milligas(gas_pairings + gas_hashing),
590+
"OnVerifyBlsAggregateSignature",
591+
gas_pairings + gas_hashing,
613592
Zero::zero(),
614593
)
615594
}

testing/calibration/shared/src/lib.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright 2021-2023 Protocol Labs
22
// SPDX-License-Identifier: Apache-2.0, MIT
3-
use fvm_shared::address::Address;
43
use fvm_shared::crypto::hash::SupportedHashes;
54
use fvm_shared::event::Flags;
65
use num_derive::FromPrimitive;
@@ -13,8 +12,8 @@ pub enum Method {
1312
OnHashing = 1,
1413
/// Put and get random data to measure `OnBlock*`.
1514
OnBlock,
16-
/// Try (and fail) to verify random data with a public key and signature.
17-
OnVerifySignature,
15+
/// Try to validate BLS aggregates (correctly).
16+
OnVerifyBlsAggregate,
1817
/// Try (and fail) to recovery a public key from a signature, using random data.
1918
OnRecoverSecpPublicKey,
2019
/// Measure sends
@@ -41,16 +40,11 @@ pub struct OnBlockParams {
4140
}
4241

4342
#[derive(Serialize, Deserialize)]
44-
pub struct OnVerifySignatureParams {
43+
pub struct OnVerifyBlsAggregateParams {
4544
pub iterations: usize,
46-
pub size: usize,
47-
pub signer: Address,
48-
/// A _valid_ signature over *something*, corresponding to the signature scheme
49-
/// of the address. A completely random sequence of bytes for signature would be
50-
/// immediately rejected by BLS, although not by Secp256k1. And we cannot generate
51-
/// valid signatures inside the contract because the libs we use don't compile to Wasm.
5245
pub signature: Vec<u8>,
53-
pub seed: u64,
46+
pub keys: Vec<Vec<u8>>,
47+
pub messages: Vec<Vec<u8>>,
5448
}
5549

5650
#[derive(Serialize, Deserialize)]

testing/integration/tests/gas_calibration_test.rs

+76-48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright 2021-2023 Protocol Labs
22
// SPDX-License-Identifier: Apache-2.0, MIT
33
mod calibration;
4-
#[cfg(feature = "calibration")]
4+
//#[cfg(feature = "calibration")]
55
use calibration::*;
6-
#[cfg(feature = "calibration")]
6+
//#[cfg(feature = "calibration")]
77
use fvm_gas_calibration_shared::*;
88

99
#[test]
@@ -375,71 +375,99 @@ fn on_send() {
375375

376376
#[test]
377377
#[cfg(feature = "calibration")]
378-
fn on_verify_signature() {
378+
fn on_verify_bls_aggregate() {
379379
use bls_signatures::Serialize;
380-
use fvm_shared::address::Address;
381-
use fvm_shared::crypto::signature::SignatureType;
382-
use rand::{thread_rng, Rng, RngCore};
383-
384-
const CHARGE_NAME: &str = "OnVerifySignature";
385-
const METHOD: Method = Method::OnVerifySignature;
380+
use rand::{thread_rng, RngCore};
386381

387-
let sig_types = vec![SignatureType::BLS, SignatureType::Secp256k1];
382+
const CHARGE_NAME: &str = "OnVerifyBlsAggregateSignature";
383+
const METHOD: Method = Method::OnVerifyBlsAggregate;
388384

389-
let sizes = common_sizes();
390385
let iterations = 100;
391386

392387
let mut te = instantiate_tester();
393388
let mut obs = Vec::new();
394389
let mut rng = thread_rng();
395390

396-
// Just some random data over which we can generate an example signature.
397-
// Having a valid BLS signature is important otherwise verification is
398-
// an instant rejection without hasing the input data.
399-
let mut data = vec![0u8; 100];
400-
rng.fill_bytes(&mut data);
391+
for &n in &[1, 4, 8, 20, 50, 200, 1000] {
392+
let mut keys = Vec::new();
393+
let mut sks = Vec::new();
394+
let mut sigs = Vec::new();
395+
let mut messages = Vec::new();
396+
for _ in 0..n {
397+
let mut data = vec![0u8; 100];
398+
rng.fill_bytes(&mut data);
399+
let sk = bls_signatures::PrivateKey::generate(&mut rng);
400+
let pk = sk.public_key();
401+
let sig = sk.sign(&data);
402+
403+
keys.push(pk.as_bytes());
404+
sks.push(sk);
405+
messages.push(data);
406+
sigs.push(sig);
407+
}
408+
let signature = bls_signatures::aggregate(&sigs).unwrap().as_bytes();
409+
let params = OnVerifyBlsAggregateParams {
410+
iterations,
411+
signature,
412+
keys,
413+
messages,
414+
};
401415

402-
for sig_type in sig_types.iter() {
403-
let label = format!("{sig_type:?}");
416+
let ret = te.execute_or_die(METHOD as u64, &params);
404417

405-
let (signer, signature) = match sig_type {
406-
SignatureType::Secp256k1 => {
407-
let sk = libsecp256k1::SecretKey::random(&mut rng);
408-
let pk = libsecp256k1::PublicKey::from_secret_key(&sk);
409-
let addr = Address::new_secp256k1(&pk.serialize()).unwrap();
410-
let sig = secp_sign(&sk, &data).into();
411-
(addr, sig)
412-
}
413-
SignatureType::BLS => {
414-
let sk = bls_signatures::PrivateKey::generate(&mut rng);
415-
let pk = sk.public_key();
416-
let addr = Address::new_bls(&pk.as_bytes()).unwrap();
417-
let sig = sk.sign(&data).as_bytes();
418-
(addr, sig)
419-
}
420-
};
418+
let iter_obs = collect_obs(&ret, CHARGE_NAME, "signers", n);
419+
let iter_obs = eliminate_outliers(iter_obs, 0.02, Eliminate::Top);
421420

422-
for size in sizes.iter() {
423-
let params = OnVerifySignatureParams {
424-
iterations,
425-
size: *size,
426-
signer,
427-
signature: signature.clone(),
428-
seed: rng.gen(),
429-
};
421+
obs.extend(iter_obs);
422+
}
430423

431-
let ret = te.execute_or_die(METHOD as u64, &params);
424+
let regression = run_linear_regression(&obs);
432425

433-
let iter_obs = collect_obs(&ret, CHARGE_NAME, &label, *size);
434-
let iter_obs = eliminate_outliers(iter_obs, 0.02, Eliminate::Top);
426+
export(CHARGE_NAME, &obs, &regression).unwrap();
427+
}
435428

436-
obs.extend(iter_obs);
437-
}
429+
#[test]
430+
#[cfg(feature = "calibration")]
431+
fn on_verify_bls_aggregate_hashing() {
432+
use bls_signatures::Serialize;
433+
use rand::{thread_rng, RngCore};
434+
435+
const CHARGE_NAME: &str = "OnVerifyBlsAggregateSignature";
436+
const METHOD: Method = Method::OnVerifyBlsAggregate;
437+
438+
let iterations = 100;
439+
let sizes = common_sizes();
440+
441+
let mut te = instantiate_tester();
442+
let mut obs = Vec::new();
443+
let mut rng = thread_rng();
444+
445+
for &size in sizes.iter() {
446+
let mut data = vec![0u8; size];
447+
rng.fill_bytes(&mut data);
448+
let sk = bls_signatures::PrivateKey::generate(&mut rng);
449+
let signature = sk.sign(&data).as_bytes();
450+
let keys = vec![sk.public_key().as_bytes()];
451+
let messages = vec![data];
452+
453+
let params = OnVerifyBlsAggregateParams {
454+
iterations,
455+
signature,
456+
keys,
457+
messages,
458+
};
459+
460+
let ret = te.execute_or_die(METHOD as u64, &params);
461+
462+
let iter_obs = collect_obs(&ret, CHARGE_NAME, "bytes", size);
463+
let iter_obs = eliminate_outliers(iter_obs, 0.02, Eliminate::Top);
464+
465+
obs.extend(iter_obs);
438466
}
439467

440468
let regression = run_linear_regression(&obs);
441469

442-
export(CHARGE_NAME, &obs, &regression).unwrap();
470+
export(&format!("{}{}", CHARGE_NAME, "Hashing"), &obs, &regression).unwrap();
443471
}
444472

445473
// Scan CBOR Fields with no links.

testing/test_actors/actors/fil-gas-calibration-actor/src/actor.rs

+9-19
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use fvm_gas_calibration_shared::*;
66
use fvm_ipld_encoding::{DAG_CBOR, IPLD_RAW};
77
use fvm_sdk::message::params_raw;
88
use fvm_sdk::vm::abort;
9-
use fvm_shared::address::{Address, Protocol};
10-
use fvm_shared::crypto::signature::{Signature, SignatureType, SECP_SIG_LEN};
9+
use fvm_shared::address::Address;
10+
use fvm_shared::crypto::signature::SECP_SIG_LEN;
1111
use fvm_shared::econ::TokenAmount;
1212
use fvm_shared::error::ExitCode;
1313
use fvm_shared::event::{ActorEvent, Entry};
@@ -48,7 +48,7 @@ fn dispatch(method: Method, params_ptr: u32) -> Result<()> {
4848
match method {
4949
Method::OnHashing => dispatch_to(on_hashing, params_ptr),
5050
Method::OnBlock => dispatch_to(on_block, params_ptr),
51-
Method::OnVerifySignature => dispatch_to(on_verify_signature, params_ptr),
51+
Method::OnVerifyBlsAggregate => dispatch_to(on_verify_bls_aggregate, params_ptr),
5252
Method::OnRecoverSecpPublicKey => dispatch_to(on_recover_secp_public_key, params_ptr),
5353
Method::OnSend => dispatch_to(on_send, params_ptr),
5454
Method::OnEvent => dispatch_to(on_event, params_ptr),
@@ -100,22 +100,12 @@ fn on_block(p: OnBlockParams) -> Result<()> {
100100
Ok(())
101101
}
102102

103-
fn on_verify_signature(p: OnVerifySignatureParams) -> Result<()> {
104-
let sig_type = match p.signer.protocol() {
105-
Protocol::BLS => SignatureType::BLS,
106-
Protocol::Secp256k1 => SignatureType::Secp256k1,
107-
other => return Err(anyhow!("unexpected protocol: {other}")),
108-
};
109-
let sig = Signature {
110-
sig_type,
111-
bytes: p.signature,
112-
};
113-
114-
let mut data = random_bytes(p.size, p.seed);
115-
116-
for i in 0..p.iterations {
117-
random_mutations(&mut data, p.seed + i as u64, MUTATION_COUNT);
118-
fvm_sdk::crypto::verify_signature(&sig, &p.signer, &data)?;
103+
fn on_verify_bls_aggregate(p: OnVerifyBlsAggregateParams) -> Result<()> {
104+
let sig = p.signature.try_into().unwrap();
105+
let keys: Vec<_> = p.keys.iter().map(|k| (&**k).try_into().unwrap()).collect();
106+
let messages: Vec<_> = p.messages.iter().map(|m| &**m).collect();
107+
for _ in 0..p.iterations {
108+
fvm_sdk::crypto::verify_bls_aggregate(&sig, &keys, &messages)?;
119109
}
120110

121111
Ok(())

0 commit comments

Comments
 (0)