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 13, 2023
1 parent d83c7e9 commit 8cabd72
Show file tree
Hide file tree
Showing 11 changed files with 685 additions and 118 deletions.
297 changes: 279 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,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: 2 additions & 0 deletions soroban-env-host/Cargo.toml
Original file line number Diff line number Diff line change
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
48 changes: 47 additions & 1 deletion soroban-env-host/src/budget.rs
Original file line number Diff line number Diff line change
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,27 @@ impl Default for BudgetImpl {
cpu.const_term = 130;
cpu.linear_term = 0;
}
// TODO: these are not yet calibrated, currently all copies of ed25519 and sha256 costs.
ContractCostType::ComputeKeccak256Hash => {
cpu.const_term = 1725;
cpu.linear_term = 33;
}
ContractCostType::ComputeEcdsaSecp256k1Key => {
cpu.const_term = 25551;
cpu.linear_term = 0;
}
ContractCostType::ComputeEcdsaSecp256k1Sig => {
cpu.const_term = 25551;
cpu.linear_term = 0;
}
ContractCostType::VerifyEcdsaSecp256k1Sig => {
cpu.const_term = 369634;
cpu.linear_term = 21;
}
ContractCostType::RecoverEcdsaSecp256k1Key => {
cpu.const_term = 369634;
cpu.linear_term = 0;
}
}

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

This comment has been minimized.

Copy link
@anupsdf

anupsdf Jun 13, 2023

Contributor

nit: Should we match ComputeSha256Hash that has 40 for const_term? Although, this would change after calibration.

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 = 0;
mem.linear_term = 0;
}
}

b.init_tracker();
Expand Down
65 changes: 42 additions & 23 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::{EnvBase, Object, RawVal, Symbol};

pub(crate) mod comparison;
mod conversion;
mod crypto;
mod data_helper;
pub(crate) mod declared_size;
pub(crate) mod error;
Expand Down Expand Up @@ -568,27 +569,6 @@ impl Host {
Ok(hash_obj)
}

pub(crate) fn verify_sig_ed25519_internal(
&self,
payload: &[u8],
public_key: &ed25519_dalek::PublicKey,
sig: &ed25519_dalek::Signature,
) -> Result<(), HostError> {
use ed25519_dalek::Verifier;
self.charge_budget(
ContractCostType::VerifyEd25519Sig,
Some(payload.len() as u64),
)?;
public_key.verify(payload, sig).map_err(|_| {
self.err(
ScErrorType::Crypto,
ScErrorCode::InvalidInput,
"failed ED25519 verification",
&[],
)
})
}

// Returns the recorded per-address authorization payloads that would cover the
// top-level contract function invocation in the enforcing mode.
// This should only be called in the recording authorization mode, i.e. only
Expand Down Expand Up @@ -1860,7 +1840,7 @@ impl VmCallerEnv for Host {
address: self.visit_obj(deployer, |addr: &ScAddress| {
addr.metered_clone(self.budget_ref())
})?,
salt: self.uint256_from_bytesobj_input("contract_id_salt", salt)?,
salt: self.u256_from_bytesobj_input("contract_id_salt", salt)?,
});
let executable =
ScContractExecutable::WasmRef(self.hash_from_bytesobj_input("wasm_hash", wasm_hash)?);
Expand Down Expand Up @@ -2394,6 +2374,16 @@ impl VmCallerEnv for Host {
self.add_host_object(self.scbytes_from_vec(hash)?)
}

// Notes on metering: covered by components.
fn compute_hash_keccak256(
&self,
_vmcaller: &mut VmCaller<Host>,
x: BytesObject,
) -> Result<BytesObject, HostError> {
let hash = self.keccak256_hash_from_bytesobj_input(x)?;
self.add_host_object(self.scbytes_from_vec(hash)?)
}

// Notes on metering: covered by components.
fn verify_sig_ed25519(
&self,
Expand All @@ -2403,13 +2393,42 @@ impl VmCallerEnv for Host {
s: BytesObject,
) -> Result<Void, HostError> {
let public_key = self.ed25519_pub_key_from_bytesobj_input(k)?;
let sig = self.signature_from_bytesobj_input("sig", s)?;
let sig = self.ed25519_signature_from_bytesobj_input("sig", s)?;
let res = self.visit_obj(x, |payload: &ScBytes| {
self.verify_sig_ed25519_internal(payload.as_slice(), &public_key, &sig)
});
Ok(res?.into())
}

// Notes on metering: covered by components.
fn verify_sig_ecdsa_secp256k1(
&self,
_vmcaller: &mut VmCaller<Host>,
public_key: BytesObject,
message: BytesObject,
signature: BytesObject,
) -> Result<Void, HostError> {
let public_key = self.secp2561k_pub_key_from_bytesobj_input(public_key)?;
let sig = self.secp2561k_signature_from_bytesobj_input(signature)?;
let res = self.visit_obj(message, |payload: &ScBytes| {
self.verify_sig_ecdsa_secp256k1_internal(payload.as_slice(), public_key, &sig)
});
Ok(res?.into())
}

fn recover_key_ecdsa_secp256k1(
&self,
_vmcaller: &mut VmCaller<Host>,
msg_digest: BytesObject,
signature: BytesObject,
recovery_id: U32Val,
) -> Result<BytesObject, HostError> {
let sig = self.secp2561k_signature_from_bytesobj_input(signature)?;
let rid = self.secp256k1_recovery_id_from_u32val(recovery_id)?;
let hash = self.hash_from_bytesobj_input("msg_digest", msg_digest)?;
self.recover_key_ecdsa_secp256k1_internal(&hash, &sig, rid)
}

fn get_ledger_version(&self, _vmcaller: &mut VmCaller<Host>) -> Result<U32Val, Self::Error> {
self.with_ledger_info(|li| Ok(li.protocol_version.into()))
}
Expand Down
73 changes: 4 additions & 69 deletions soroban-env-host/src/host/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ use crate::err;
use crate::host_object::{HostMap, HostObject, HostVec};
use crate::xdr::{Hash, LedgerKey, LedgerKeyContractData, ScVal, ScVec, Uint256};
use crate::{xdr::ContractCostType, Host, HostError, RawVal};
use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH};
use sha2::{Digest, Sha256};
use soroban_env_common::num::{
i256_from_pieces, i256_into_pieces, u256_from_pieces, u256_into_pieces,
};
Expand Down Expand Up @@ -65,10 +63,7 @@ impl Host {
}
}

pub(crate) fn to_u256_from_account(
&self,
account_id: &AccountId,
) -> Result<Uint256, HostError> {
pub(crate) fn u256_from_account(&self, account_id: &AccountId) -> Result<Uint256, HostError> {
let crate::xdr::PublicKey::PublicKeyTypeEd25519(ed25519) =
account_id.metered_clone(&self.0.budget)?.0;
Ok(ed25519)
Expand Down Expand Up @@ -100,31 +95,15 @@ impl Host {
self.fixed_length_bytes_from_bytesobj_input::<Hash, 32>(name, hash)
}

pub(crate) fn uint256_from_bytesobj_input(
pub(crate) fn u256_from_bytesobj_input(
&self,
name: &'static str,
u256: BytesObject,
) -> Result<Uint256, HostError> {
self.fixed_length_bytes_from_bytesobj_input::<Uint256, 32>(name, u256)
}

pub(crate) fn signature_from_bytes(
&self,
name: &'static str,
bytes: &[u8],
) -> Result<Signature, HostError> {
self.fixed_length_bytes_from_slice::<Signature, SIGNATURE_LENGTH>(name, bytes)
}

pub(crate) fn signature_from_bytesobj_input(
&self,
name: &'static str,
sig: BytesObject,
) -> Result<Signature, HostError> {
self.fixed_length_bytes_from_bytesobj_input::<Signature, SIGNATURE_LENGTH>(name, sig)
}

fn fixed_length_bytes_from_slice<T, const N: usize>(
pub(crate) fn fixed_length_bytes_from_slice<T, const N: usize>(
&self,
name: &'static str,
bytes_arr: &[u8],
Expand All @@ -148,7 +127,7 @@ impl Host {
}
}

fn fixed_length_bytes_from_bytesobj_input<T, const N: usize>(
pub(crate) fn fixed_length_bytes_from_bytesobj_input<T, const N: usize>(
&self,
name: &'static str,
obj: BytesObject,
Expand All @@ -161,27 +140,6 @@ impl Host {
})
}

pub(crate) fn ed25519_pub_key_from_bytes(&self, bytes: &[u8]) -> Result<PublicKey, HostError> {
self.charge_budget(ContractCostType::ComputeEd25519PubKey, None)?;
PublicKey::from_bytes(bytes).map_err(|_| {
err!(
self,
(ScErrorType::Crypto, ScErrorCode::InvalidInput),
"invalid ed25519 public key",
bytes
)
})
}

pub fn ed25519_pub_key_from_bytesobj_input(
&self,
k: BytesObject,
) -> Result<PublicKey, HostError> {
self.visit_obj(k, |bytes: &ScBytes| {
self.ed25519_pub_key_from_bytes(bytes.as_slice())
})
}

pub(crate) fn account_id_from_bytesobj(&self, k: BytesObject) -> Result<AccountId, HostError> {
self.visit_obj(k, |bytes: &ScBytes| {
Ok(AccountId(xdr::PublicKey::PublicKeyTypeEd25519(
Expand All @@ -190,29 +148,6 @@ impl Host {
})
}

pub(crate) fn sha256_hash_from_bytes(&self, bytes: &[u8]) -> Result<Vec<u8>, HostError> {
self.charge_budget(
ContractCostType::ComputeSha256Hash,
Some(bytes.len() as u64),
)?;
Ok(Sha256::digest(bytes).as_slice().to_vec())
}

pub fn sha256_hash_from_bytesobj_input(&self, x: BytesObject) -> Result<Vec<u8>, HostError> {
self.visit_obj(x, |bytes: &ScBytes| {
let hash = self.sha256_hash_from_bytes(bytes.as_slice())?;
if hash.len() != 32 {
return Err(err!(
self,
(ScErrorType::Object, ScErrorCode::UnexpectedSize),
"expected 32-byte BytesObject for hash, got different size",
hash.len()
));
}
Ok(hash)
})
}

/// Converts a [`RawVal`] to an [`ScVal`] and combines it with the currently-executing
/// [`ContractID`] to produce a [`Key`], that can be used to access ledger [`Storage`].
// Notes on metering: covered by components.
Expand Down
Loading

0 comments on commit 8cabd72

Please sign in to comment.