Skip to content

Commit

Permalink
Add secp256k1 and keccak256 host functions
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon committed Jun 15, 2023
1 parent 3dfc216 commit 675ff1f
Show file tree
Hide file tree
Showing 45 changed files with 1,505 additions and 257 deletions.
297 changes: 279 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -27,7 +27,7 @@ soroban-native-sdk-macros = { version = "0.0.16", path = "soroban-native-sdk-mac
[workspace.dependencies.stellar-xdr]
version = "0.0.16"
git = "https://github.com/stellar/rs-stellar-xdr"
rev = "3429ea634df58aadf836790de8bbea21b7e82651"
rev = "eafa8b74e3814d738e2bd948b556f8f3c8a76659"
default-features = false

[workspace.dependencies.wasmi]
Expand Down
52 changes: 52 additions & 0 deletions soroban-env-common/env.json
Expand Up @@ -1548,6 +1548,58 @@
}
],
"return": "Void"
},
{
"export": "1",
"name": "compute_hash_keccak256",
"args": [
{
"name": "x",
"type": "BytesObject"
}
],
"return": "BytesObject",
"docs": "Returns the keccak256 hash of given input bytes."
},
{
"export": "2",
"name": "verify_sig_ecdsa_secp256k1",
"args": [
{
"name": "public_key",
"type": "BytesObject"
},
{
"name": "message",
"type": "BytesObject"
},
{
"name": "signature",
"type": "BytesObject"
}
],
"return": "Void",
"docs": "Verifies that a given SEC-1-encoded ECDSA secp256k1 public key, signing a given message, produced a given 64-byte signature."
},
{
"export": "3",
"name": "recover_key_ecdsa_secp256k1",
"args": [
{
"name": "msg_digest",
"type": "BytesObject"
},
{
"name": "signature",
"type": "BytesObject"
},
{
"name": "recovery_id",
"type": "U32Val"
}
],
"return": "BytesObject",
"docs": "Recovers the SEC-1-encoded ECDSA secp256k1 public key that produced a given 64-byte signature over a given 32-byte message digest, for a given recovery_id byte."
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion soroban-env-common/src/meta.rs
Expand Up @@ -42,7 +42,7 @@ pub const ENV_META_V0_SECTION_NAME: &str = "contractenvmetav0";

soroban_env_macros::generate_env_meta_consts!(
ledger_protocol_version: 20,
pre_release_version: 43,
pre_release_version: 44,
);

pub fn get_ledger_protocol_version(interface_version: u64) -> u32 {
Expand Down
2 changes: 2 additions & 0 deletions soroban-env-host/Cargo.toml
Expand Up @@ -29,6 +29,8 @@ num-integer = "0.1.45"
num-derive = "0.3.3"
log = "0.4.17"
backtrace = "0.3"
k256 = {version = "0.13.1", features=["ecdsa", "arithmetic"]}
sha3 = "0.10.8"

[dev-dependencies]
env_logger = "0.9.0"
Expand Down
@@ -0,0 +1,26 @@
use crate::common::HostCostMeasurement;
use k256::{PublicKey, SecretKey};
use rand::rngs::StdRng;
use soroban_env_host::{cost_runner::ComputeEcdsaSecp256k1PubKeyRun, Host};

// This measures the costs to turn one byte buffer into an EcdsaSecp256k1
// pubkey, which should be constant time. The input value is ignored.
pub(crate) struct ComputeEcdsaSecp256k1PubKeyMeasure {
key: Vec<u8>,
}

impl HostCostMeasurement for ComputeEcdsaSecp256k1PubKeyMeasure {
type Runner = ComputeEcdsaSecp256k1PubKeyRun;

fn new_random_case(_host: &Host, _rng: &mut StdRng, _input: u64) -> Vec<u8> {
// Very awkward: the 'rand' crate has two copies linked in due to
// divergence between the requirements of k256 and ed25519. The StdRng
// we're getting here is not the one k256 wants. So we use an OsRng
// here, from the package k256 wants (and re-exports).
let mut rng = k256::elliptic_curve::rand_core::OsRng;

let secret = SecretKey::random(&mut rng);
let public: PublicKey = secret.public_key();
public.to_sec1_bytes().into_vec()
}
}
@@ -0,0 +1,32 @@
use crate::common::HostCostMeasurement;
use k256::{
ecdsa::{signature::Signer, Signature, SigningKey},
SecretKey,
};
use rand::rngs::StdRng;
use soroban_env_host::{cost_runner::ComputeEcdsaSecp256k1SigRun, Host};

// This measures the costs to turn one byte buffer into an EcdsaSecp256k1
// signature, which should be constant time. The input value is ignored.
pub(crate) struct ComputeEcdsaSecp256k1SigMeasure {
sig: Vec<u8>,
}

impl HostCostMeasurement for ComputeEcdsaSecp256k1SigMeasure {
type Runner = ComputeEcdsaSecp256k1SigRun;

fn new_random_case(_host: &Host, _rng: &mut StdRng, input: u64) -> Vec<u8> {
let size = 1 + input * Self::STEP_SIZE;

// Very awkward: the 'rand' crate has two copies linked in due to
// divergence between the requirements of k256 and ed25519. The StdRng
// we're getting here is not the one k256 wants. So we use an OsRng
// here, from the package k256 wants (and re-exports).
let mut rng = k256::elliptic_curve::rand_core::OsRng;

let sec: SecretKey = SecretKey::random(&mut rng);
let msg: Vec<u8> = (0..size).map(|x| x as u8).collect();
let sig: Signature = SigningKey::from(sec).try_sign(msg.as_slice()).unwrap();
sig.to_bytes().to_vec()
}
}
@@ -0,0 +1,19 @@
use crate::common::HostCostMeasurement;
use rand::rngs::StdRng;
use soroban_env_host::{cost_runner::ComputeKeccak256HashRun, Host};

// This measures the costs of performing a keccak256 hash on a variable-sized
// byte buffer. The input value is the size of the buffer. It should be
// linear time.
pub(crate) struct ComputeKeccak256HashMeasure;

impl HostCostMeasurement for ComputeKeccak256HashMeasure {
type Runner = ComputeKeccak256HashRun;

const STEP_SIZE: u64 = 100;

fn new_random_case(_host: &Host, _rng: &mut StdRng, input: u64) -> Vec<u8> {
let size = 1 + input * Self::STEP_SIZE;
(0..size).map(|n| n as u8).collect()
}
}
10 changes: 10 additions & 0 deletions soroban-env-host/benches/common/cost_types/mod.rs
@@ -1,34 +1,44 @@
mod charge_budget;
mod compute_ecdsa_secp256k1_pubkey;
mod compute_ecdsa_secp256k1_sig;
mod compute_ed25519_pubkey;
mod compute_keccak256_hash;
mod compute_sha256_hash;
mod guard_frame;
mod host_mem_alloc;
mod host_mem_cmp;
mod host_mem_cpy;
mod invoke;
mod map_ops;
mod recover_ecdsa_secp256k1_key;
mod val_deser;
mod val_ser;
mod val_xdr_conv;
mod vec_ops;
mod verify_ecdsa_secp256k1_sig;
mod verify_ed25519_sig;
mod visit_object;
mod vm_ops;
mod wasm_insn_exec;

pub(crate) use charge_budget::*;
pub(crate) use compute_ecdsa_secp256k1_pubkey::*;
pub(crate) use compute_ecdsa_secp256k1_sig::*;
pub(crate) use compute_ed25519_pubkey::*;
pub(crate) use compute_keccak256_hash::*;
pub(crate) use compute_sha256_hash::*;
pub(crate) use guard_frame::*;
pub(crate) use host_mem_alloc::*;
pub(crate) use host_mem_cmp::*;
pub(crate) use host_mem_cpy::*;
pub(crate) use invoke::*;
pub(crate) use map_ops::*;
pub(crate) use recover_ecdsa_secp256k1_key::*;
pub(crate) use val_deser::*;
pub(crate) use val_ser::*;
pub(crate) use val_xdr_conv::*;
pub(crate) use vec_ops::*;
pub(crate) use verify_ecdsa_secp256k1_sig::*;
pub(crate) use verify_ed25519_sig::*;
pub(crate) use visit_object::*;
pub(crate) use vm_ops::*;
Expand Down
@@ -0,0 +1,41 @@
use crate::common::HostCostMeasurement;
use k256::{ecdsa::SigningKey, SecretKey};
use rand::rngs::StdRng;
use sha3::{Digest, Keccak256};
use soroban_env_host::{
cost_runner::{RecoverEcdsaSecp256k1KeyRun, RecoverEcdsaSecp256k1KeySample},
xdr::Hash,
Host,
};

pub(crate) struct RecoverEcdsaSecp256k1KeyMeasure;

// This measures the cost of verifying an EcdsaSecp256k1 signature of varying-length
// messages. The input value is the length of the signed message. It should cost
// linear CPU (for hashing) and zero heap memory.
impl HostCostMeasurement for RecoverEcdsaSecp256k1KeyMeasure {
type Runner = RecoverEcdsaSecp256k1KeyRun;

const STEP_SIZE: u64 = 1000;

fn new_random_case(
_host: &Host,
_rng: &mut StdRng,
input: u64,
) -> RecoverEcdsaSecp256k1KeySample {
// Very awkward: the 'rand' crate has two copies linked in due to
// divergence between the requirements of k256 and ed25519. The StdRng
// we're getting here is not the one k256 wants. So we use an OsRng
// here, from the package k256 wants (and re-exports).
let mut rng = k256::elliptic_curve::rand_core::OsRng;

let size = 1 + input * Self::STEP_SIZE;
let sec: SecretKey = SecretKey::random(&mut rng);
let msg: Vec<u8> = (0..size).map(|x| x as u8).collect();
let hash: Hash = Hash(Keccak256::digest(msg).into());
let (sig, rid) = SigningKey::from(sec)
.sign_prehash_recoverable(hash.as_slice())
.unwrap();
RecoverEcdsaSecp256k1KeySample { hash, sig, rid }
}
}
@@ -0,0 +1,41 @@
use crate::common::HostCostMeasurement;
use k256::{
ecdsa::{signature::Signer, Signature, SigningKey},
PublicKey, SecretKey,
};
use rand::rngs::StdRng;
use soroban_env_host::{
cost_runner::{VerifyEcdsaSecp256k1SigRun, VerifyEcdsaSecp256k1SigSample},
Host,
};

pub(crate) struct VerifyEcdsaSecp256k1SigMeasure;

// This measures the cost of verifying an EcdsaSecp256k1 signature of varying-length
// messages. The input value is the length of the signed message. It should cost
// linear CPU (for hashing) and zero heap memory.
impl HostCostMeasurement for VerifyEcdsaSecp256k1SigMeasure {
type Runner = VerifyEcdsaSecp256k1SigRun;

const STEP_SIZE: u64 = 1000;

fn new_random_case(
_host: &Host,
_rng: &mut StdRng,
input: u64,
) -> VerifyEcdsaSecp256k1SigSample {
let size = 1 + input * Self::STEP_SIZE;

// Very awkward: the 'rand' crate has two copies linked in due to
// divergence between the requirements of k256 and ed25519. The StdRng
// we're getting here is not the one k256 wants. So we use an OsRng
// here, from the package k256 wants (and re-exports).
let mut rng = k256::elliptic_curve::rand_core::OsRng;

let sec: SecretKey = SecretKey::random(&mut rng);
let key: PublicKey = sec.public_key();
let msg: Vec<u8> = (0..size).map(|x| x as u8).collect();
let sig: Signature = SigningKey::from(sec).try_sign(msg.as_slice()).unwrap();
VerifyEcdsaSecp256k1SigSample { key, msg, sig }
}
}
5 changes: 5 additions & 0 deletions soroban-env-host/benches/common/mod.rs
Expand Up @@ -62,8 +62,13 @@ pub(crate) fn for_each_host_cost_measurement<B: Benchmark>(
) -> std::io::Result<BTreeMap<ContractCostType, (FPCostModel, FPCostModel)>> {
let mut params: BTreeMap<ContractCostType, (FPCostModel, FPCostModel)> = BTreeMap::new();

call_bench::<B, ComputeEcdsaSecp256k1PubKeyMeasure>(&mut params)?;
call_bench::<B, ComputeEcdsaSecp256k1SigMeasure>(&mut params)?;
call_bench::<B, ComputeEd25519PubKeyMeasure>(&mut params)?;
call_bench::<B, ComputeKeccak256HashMeasure>(&mut params)?;
call_bench::<B, ComputeSha256HashMeasure>(&mut params)?;
call_bench::<B, RecoverEcdsaSecp256k1KeyMeasure>(&mut params)?;
call_bench::<B, VerifyEcdsaSecp256k1SigMeasure>(&mut params)?;
call_bench::<B, VerifyEd25519SigMeasure>(&mut params)?;
call_bench::<B, VmInstantiationMeasure>(&mut params)?;
call_bench::<B, VmMemReadMeasure>(&mut params)?;
Expand Down
47 changes: 46 additions & 1 deletion soroban-env-host/src/budget.rs
Expand Up @@ -304,12 +304,17 @@ impl BudgetImpl {
ContractCostType::MapEntry => (),
ContractCostType::VecEntry => (),
ContractCostType::GuardFrame => (),
ContractCostType::VerifyEd25519Sig => self.tracker[i].1 = Some(0), // length of the signature buffer
ContractCostType::VerifyEd25519Sig => self.tracker[i].1 = Some(0), // length of the signed message
ContractCostType::VmMemRead => self.tracker[i].1 = Some(0), // number of bytes in the linear memory to read
ContractCostType::VmMemWrite => self.tracker[i].1 = Some(0), // number of bytes in the linear memory to write
ContractCostType::VmInstantiation => self.tracker[i].1 = Some(0), // length of the wasm bytes,
ContractCostType::InvokeVmFunction => (),
ContractCostType::ChargeBudget => (),
ContractCostType::ComputeKeccak256Hash => self.tracker[i].1 = Some(0), // number of bytes in the buffer
ContractCostType::ComputeEcdsaSecp256k1Key => (),
ContractCostType::ComputeEcdsaSecp256k1Sig => (),
ContractCostType::VerifyEcdsaSecp256k1Sig => self.tracker[i].1 = Some(0), // length of the signed message
ContractCostType::RecoverEcdsaSecp256k1Key => (),
}
}
}
Expand Down Expand Up @@ -804,6 +809,26 @@ impl Default for BudgetImpl {
cpu.const_term = 130;
cpu.linear_term = 0;
}
ContractCostType::ComputeKeccak256Hash => {
cpu.const_term = 3322;
cpu.linear_term = 46;
}
ContractCostType::ComputeEcdsaSecp256k1Key => {
cpu.const_term = 56525;
cpu.linear_term = 0;
}
ContractCostType::ComputeEcdsaSecp256k1Sig => {
cpu.const_term = 250;
cpu.linear_term = 0;
}
ContractCostType::VerifyEcdsaSecp256k1Sig => {
cpu.const_term = 1109918;
cpu.linear_term = 53;
}
ContractCostType::RecoverEcdsaSecp256k1Key => {
cpu.const_term = 2319640;
cpu.linear_term = 0;
}
}

// define the memory cost model parameters
Expand Down Expand Up @@ -897,6 +922,26 @@ impl Default for BudgetImpl {
mem.const_term = 0;
mem.linear_term = 0;
}
ContractCostType::ComputeKeccak256Hash => {
mem.const_term = 40;
mem.linear_term = 0;
}
ContractCostType::ComputeEcdsaSecp256k1Key => {
mem.const_term = 0;
mem.linear_term = 0;
}
ContractCostType::ComputeEcdsaSecp256k1Sig => {
mem.const_term = 0;
mem.linear_term = 0;
}
ContractCostType::VerifyEcdsaSecp256k1Sig => {
mem.const_term = 0;
mem.linear_term = 0;
}
ContractCostType::RecoverEcdsaSecp256k1Key => {
mem.const_term = 181;
mem.linear_term = 0;
}
}

b.init_tracker();
Expand Down

0 comments on commit 675ff1f

Please sign in to comment.