diff --git a/.gitignore b/.gitignore index 54d550f97..de8745fa0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target .vscode /vendor -lcov.info \ No newline at end of file +lcov.info +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4c7bc59af..a4eae01c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,9 +702,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", - "signature", ] [[package]] @@ -942,6 +940,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.14" @@ -1011,6 +1021,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "primeorder" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7dbe9ed3b56368bd99483eb32fe9c17fdd3730aebadc906918ce78d54c7eeb4" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "2.0.0" @@ -1385,8 +1404,11 @@ dependencies = [ "backtrace", "bytes-lit", "curve25519-dalek", + "ecdsa", "ed25519-dalek", + "elliptic-curve", "expect-test", + "generic-array", "getrandom", "hex", "hex-literal", @@ -1399,10 +1421,12 @@ dependencies = [ "num-derive", "num-integer", "num-traits", + "p256", "pretty_assertions", "rand", "rand_chacha", "rustversion", + "sec1", "serde_json", "sha2", "sha3", @@ -1423,6 +1447,7 @@ dependencies = [ "wasm-encoder", "wasmparser", "wasmprinter", + "wycheproof", ] [[package]] @@ -1517,7 +1542,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "20.1.0" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=44b7e2d4cdf27a3611663e82828de56c5274cba0#44b7e2d4cdf27a3611663e82828de56c5274cba0" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=3a001b1fbb20e4cfa2cef2c0cc450564e8528057#3a001b1fbb20e4cfa2cef2c0cc450564e8528057" dependencies = [ "arbitrary", "base64 0.13.1", @@ -2026,6 +2051,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "wycheproof" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e639f57253b80c6584b378011aec0fed61c4c21d7a4b97c4d9d7eaf35ca77d12" +dependencies = [ + "base64 0.21.5", + "hex", + "serde", + "serde_json", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index fc3ae86ff..21298f25d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ wasmparser = "=0.116.1" [workspace.dependencies.stellar-xdr] version = "=20.1.0" git = "https://github.com/stellar/rs-stellar-xdr" -rev = "44b7e2d4cdf27a3611663e82828de56c5274cba0" +rev = "3a001b1fbb20e4cfa2cef2c0cc450564e8528057" default-features = false [workspace.dependencies.wasmi] diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json index f74f1a94d..ec4355825 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -2000,7 +2000,28 @@ } ], "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." + "docs": "Recovers the SEC-1-encoded ECDSA secp256k1 public key that produced a given 64-byte `signature` over a given 32-byte `msg_digest` for a given `recovery_id` byte. Warning: The `msg_digest` must be produced by a secure cryptographic hash function on the message, otherwise the attacker can potentially forge signatures. The `signature` is the ECDSA signature `(r, s)` serialized as fixed-size big endian scalar values, both `r`, `s` must be non-zero and `s` must be in the lower range. Returns a `BytesObject` containing 65-bytes representing SEC-1 encoded point in uncompressed format. The `recovery_id` is an integer value `0`, `1`, `2`, or `3`, the low bit (0/1) indicates the parity of the y-coordinate of the `public_key` (even/odd) and the high bit (3/4) indicate if the `r` (x-coordinate of `k x G`) has overflown during its computation." + }, + { + "export": "3", + "name": "verify_sig_ecdsa_secp256r1", + "args": [ + { + "name": "public_key", + "type": "BytesObject" + }, + { + "name": "msg_digest", + "type": "BytesObject" + }, + { + "name": "signature", + "type": "BytesObject" + } + ], + "return": "Void", + "docs": "Verifies the `signature` using an ECDSA secp256r1 `public_key` on a 32-byte `msg_digest`. Warning: The `msg_digest` must be produced by a secure cryptographic hash function on the message, otherwise the attacker can potentially forge signatures. The `public_key` is expected to be 65 bytes in length, representing a SEC-1 encoded point in uncompressed format. The `signature` is the ECDSA signature `(r, s)` serialized as fixed-size big endian scalar values, both `r`, `s` must be non-zero and `s` must be in the lower range. ", + "min_supported_protocol": 21 } ] }, diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml index e0f1f952c..a06788501 100644 --- a/soroban-env-host/Cargo.toml +++ b/soroban-env-host/Cargo.toml @@ -32,7 +32,12 @@ num-traits = "=0.2.17" num-integer = "=0.1.45" num-derive = "=0.4.1" backtrace = { version = "=0.3.69", optional = true } -k256 = {version = "=0.13.1", features=["ecdsa", "arithmetic"]} +k256 = {version = "=0.13.1", default-features = false, features = ["ecdsa", "arithmetic"]} +p256 = {version = "=0.13.2", default-features = false, features = ["ecdsa", "arithmetic"]} +ecdsa = {version = "=0.16.8", default-features = false} +sec1 = {version = "=0.7.3"} +elliptic-curve ={ version = "0.13.6", default-features = false} +generic-array ={ version = "0.14.7"} # NB: getrandom is a transitive dependency of k256 which we're not using directly # but we have to specify it here in order to enable its 'js' feature which # is needed to build the host for wasm (a rare but supported config). @@ -83,11 +88,14 @@ lstsq = "=0.5.0" nalgebra = { version = "=0.32.3", default-features = false, features = ["std"]} wasm-encoder = "=0.36.2" rustversion = "1.0" +wycheproof = "=0.5.1" +k256 = {version = "=0.13.1", default-features = false, features = ["alloc"]} +p256 = {version = "=0.13.2", default-features = false, features = ["alloc"]} [dev-dependencies.stellar-xdr] version = "=20.1.0" git = "https://github.com/stellar/rs-stellar-xdr" -rev = "44b7e2d4cdf27a3611663e82828de56c5274cba0" +rev = "3a001b1fbb20e4cfa2cef2c0cc450564e8528057" default-features = false features = ["arbitrary"] diff --git a/soroban-env-host/benches/common/cost_types/decode_ecdsa_curve256_sig.rs b/soroban-env-host/benches/common/cost_types/decode_ecdsa_curve256_sig.rs new file mode 100644 index 000000000..fae581803 --- /dev/null +++ b/soroban-env-host/benches/common/cost_types/decode_ecdsa_curve256_sig.rs @@ -0,0 +1,39 @@ +use crate::common::HostCostMeasurement; +use ecdsa::signature::hazmat::PrehashSigner; +use elliptic_curve::scalar::IsHigh; +use k256::{ + ecdsa::{Signature, SigningKey}, + Secp256k1, +}; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{DecodeEcdsaCurve256SigRun, DecodeEcdsaCurve256SigSample}, + Host, +}; + +pub(crate) struct DecodeEcdsaCurve256SigMeasure; + +impl HostCostMeasurement for DecodeEcdsaCurve256SigMeasure { + type Runner = DecodeEcdsaCurve256SigRun; + + fn new_random_case( + _host: &Host, + rng: &mut StdRng, + _input: u64, + ) -> DecodeEcdsaCurve256SigSample { + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let mut msg_hash = [0u8; 32]; + rng.fill_bytes(&mut msg_hash); + let mut sig: Signature = signer.sign_prehash(&msg_hash).unwrap(); + // in our host implementation, we are rejecting high `S`. so here we + // normalize it to the low S before sending the result + if bool::from(sig.s().is_high()) { + sig = sig.normalize_s().unwrap(); + } + DecodeEcdsaCurve256SigSample { + bytes: sig.to_vec(), + } + } +} diff --git a/soroban-env-host/benches/common/cost_types/mod.rs b/soroban-env-host/benches/common/cost_types/mod.rs index 75dde9a84..71c82f441 100644 --- a/soroban-env-host/benches/common/cost_types/mod.rs +++ b/soroban-env-host/benches/common/cost_types/mod.rs @@ -1,7 +1,10 @@ +#[cfg(not(feature = "next"))] mod compute_ecdsa_secp256k1_sig; mod compute_ed25519_pubkey; mod compute_keccak256_hash; mod compute_sha256_hash; +#[cfg(feature = "next")] +mod decode_ecdsa_curve256_sig; mod host_mem_alloc; mod host_mem_cmp; mod host_mem_cpy; @@ -9,17 +12,24 @@ mod invoke; mod num_ops; mod prng; mod recover_ecdsa_secp256k1_key; +#[cfg(feature = "next")] +mod sec1_decode_point_uncompressed; mod val_deser; mod val_ser; +#[cfg(feature = "next")] +mod verify_ecdsa_secp256r1_sig; mod verify_ed25519_sig; mod visit_object; mod vm_ops; mod wasm_insn_exec; +#[cfg(not(feature = "next"))] 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::*; +#[cfg(feature = "next")] +pub(crate) use decode_ecdsa_curve256_sig::*; pub(crate) use host_mem_alloc::*; pub(crate) use host_mem_cmp::*; pub(crate) use host_mem_cpy::*; @@ -27,8 +37,12 @@ pub(crate) use invoke::*; pub(crate) use num_ops::*; pub(crate) use prng::*; pub(crate) use recover_ecdsa_secp256k1_key::*; +#[cfg(feature = "next")] +pub(crate) use sec1_decode_point_uncompressed::*; pub(crate) use val_deser::*; pub(crate) use val_ser::*; +#[cfg(feature = "next")] +pub(crate) use verify_ecdsa_secp256r1_sig::*; pub(crate) use verify_ed25519_sig::*; pub(crate) use visit_object::*; pub(crate) use vm_ops::*; diff --git a/soroban-env-host/benches/common/cost_types/sec1_decode_point_uncompressed.rs b/soroban-env-host/benches/common/cost_types/sec1_decode_point_uncompressed.rs new file mode 100644 index 000000000..7a2ae6798 --- /dev/null +++ b/soroban-env-host/benches/common/cost_types/sec1_decode_point_uncompressed.rs @@ -0,0 +1,24 @@ +use crate::common::HostCostMeasurement; +use p256::ecdsa::SigningKey; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{Sec1DecodePointSample, Sec1DecodePointUncompressedRun}, + Host, +}; + +pub(crate) struct Sec1DecodePointUncompressedMeasure {} + +impl HostCostMeasurement for Sec1DecodePointUncompressedMeasure { + type Runner = Sec1DecodePointUncompressedRun; + + fn new_random_case(_host: &Host, rng: &mut StdRng, _input: u64) -> Sec1DecodePointSample { + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let verifying_key = signer.verifying_key(); + let bytes = verifying_key + .to_encoded_point(false /* compress */) + .to_bytes(); + Sec1DecodePointSample { bytes } + } +} diff --git a/soroban-env-host/benches/common/cost_types/verify_ecdsa_secp256r1_sig.rs b/soroban-env-host/benches/common/cost_types/verify_ecdsa_secp256r1_sig.rs new file mode 100644 index 000000000..2eb456b13 --- /dev/null +++ b/soroban-env-host/benches/common/cost_types/verify_ecdsa_secp256r1_sig.rs @@ -0,0 +1,38 @@ +use crate::common::HostCostMeasurement; +use ecdsa::signature::hazmat::PrehashSigner; +use elliptic_curve::scalar::IsHigh; +use p256::ecdsa::{Signature, SigningKey}; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{VerifyEcdsaSecp256r1SigRun, VerifyEcdsaSecp256r1SigSample}, + xdr::Hash, + Host, +}; + +pub(crate) struct VerifyEcdsaSecp256r1SigMeasure {} + +impl HostCostMeasurement for VerifyEcdsaSecp256r1SigMeasure { + type Runner = VerifyEcdsaSecp256r1SigRun; + + fn new_random_case( + _host: &Host, + rng: &mut StdRng, + _input: u64, + ) -> VerifyEcdsaSecp256r1SigSample { + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let mut msg_hash = [0u8; 32]; + rng.fill_bytes(&mut msg_hash); + let mut sig: Signature = signer.sign_prehash(&msg_hash).unwrap(); + // in our host implementation, we are rejecting high `s`, we are doing it here too. + if bool::from(sig.s().is_high()) { + sig = sig.normalize_s().unwrap() + } + VerifyEcdsaSecp256r1SigSample { + pub_key: signer.verifying_key().clone(), + msg_hash: Hash::from(msg_hash), + sig, + } + } +} diff --git a/soroban-env-host/benches/common/experimental/decode_secp256r1_sig.rs b/soroban-env-host/benches/common/experimental/decode_secp256r1_sig.rs new file mode 100644 index 000000000..841fac140 --- /dev/null +++ b/soroban-env-host/benches/common/experimental/decode_secp256r1_sig.rs @@ -0,0 +1,36 @@ +use crate::common::HostCostMeasurement; +use ecdsa::signature::hazmat::PrehashSigner; +use elliptic_curve::scalar::IsHigh; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{DecodeEcdsaCurve256SigSample, DecodeSecp256r1SigRun}, + Host, +}; + +pub(crate) struct DecodeSecp256r1SigMeasure {} + +impl HostCostMeasurement for DecodeSecp256r1SigMeasure { + type Runner = DecodeSecp256r1SigRun; + + fn new_random_case( + _host: &Host, + rng: &mut StdRng, + _input: u64, + ) -> DecodeEcdsaCurve256SigSample { + use p256::ecdsa::{Signature, SigningKey}; + + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let mut msg_hash = [0u8; 32]; + rng.fill_bytes(&mut msg_hash); + let mut sig: Signature = signer.sign_prehash(&msg_hash).unwrap(); + // in our host implementation, we are rejecting high `s`, we are doing it here too. + if bool::from(sig.s().is_high()) { + sig = sig.normalize_s().unwrap(); + } + DecodeEcdsaCurve256SigSample { + bytes: sig.to_vec(), + } + } +} diff --git a/soroban-env-host/benches/common/experimental/ecdsa_secp256k1_verify.rs b/soroban-env-host/benches/common/experimental/ecdsa_secp256k1_verify.rs new file mode 100644 index 000000000..0a4b8cd21 --- /dev/null +++ b/soroban-env-host/benches/common/experimental/ecdsa_secp256k1_verify.rs @@ -0,0 +1,34 @@ +use crate::common::HostCostMeasurement; +use ecdsa::signature::hazmat::PrehashSigner; +use elliptic_curve::scalar::IsHigh; +use k256::ecdsa::{Signature, SigningKey}; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{EcdsaSecp256k1VerifyRun, EcdsaSecp256k1VerifySample}, + xdr::Hash, + Host, +}; + +pub(crate) struct EcdsaSecp256k1VerifyMeasure {} + +impl HostCostMeasurement for EcdsaSecp256k1VerifyMeasure { + type Runner = EcdsaSecp256k1VerifyRun; + + fn new_random_case(_host: &Host, rng: &mut StdRng, _input: u64) -> EcdsaSecp256k1VerifySample { + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let mut msg_hash = [0u8; 32]; + rng.fill_bytes(&mut msg_hash); + let mut sig: Signature = signer.sign_prehash(&msg_hash).unwrap(); + // in our host implementation, we are rejecting high `s`, we are doing it here too. + if bool::from(sig.s().is_high()) { + sig = sig.normalize_s().unwrap() + } + EcdsaSecp256k1VerifySample { + pub_key: signer.verifying_key().clone(), + msg_hash: Hash::from(msg_hash), + sig, + } + } +} diff --git a/soroban-env-host/benches/common/experimental/ecdsa_secp256r1_recover.rs b/soroban-env-host/benches/common/experimental/ecdsa_secp256r1_recover.rs new file mode 100644 index 000000000..9a51a7c89 --- /dev/null +++ b/soroban-env-host/benches/common/experimental/ecdsa_secp256r1_recover.rs @@ -0,0 +1,33 @@ +use crate::common::HostCostMeasurement; +use elliptic_curve::scalar::IsHigh; +use p256::ecdsa::SigningKey; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{EcdsaSecp256r1RecoverRun, EcdsaSecp256r1RecoverSample}, + xdr::Hash, + Host, +}; + +pub(crate) struct EcdsaSecp256r1RecoverMeasure {} + +impl HostCostMeasurement for EcdsaSecp256r1RecoverMeasure { + type Runner = EcdsaSecp256r1RecoverRun; + + fn new_random_case(_host: &Host, rng: &mut StdRng, _input: u64) -> EcdsaSecp256r1RecoverSample { + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let mut msg_hash = [0u8; 32]; + rng.fill_bytes(&mut msg_hash); + let (mut sig, recovery_id) = signer.sign_prehash_recoverable(&msg_hash).unwrap(); + // in our host implementation, we are rejecting high `s`, we are doing it here too. + if bool::from(sig.s().is_high()) { + sig = sig.normalize_s().unwrap() + } + EcdsaSecp256r1RecoverSample { + msg_hash: Hash::from(msg_hash), + sig, + recovery_id, + } + } +} diff --git a/soroban-env-host/benches/common/experimental/mod.rs b/soroban-env-host/benches/common/experimental/mod.rs index aa11efb15..a0e699d53 100644 --- a/soroban-env-host/benches/common/experimental/mod.rs +++ b/soroban-env-host/benches/common/experimental/mod.rs @@ -1,5 +1,17 @@ +#[cfg(feature = "next")] +mod decode_secp256r1_sig; +mod ecdsa_secp256k1_verify; +mod ecdsa_secp256r1_recover; mod ed25519_scalar_mul; mod read_xdr; +#[cfg(feature = "next")] +mod sec1_decode_point_compressed; +#[cfg(feature = "next")] +pub(crate) use decode_secp256r1_sig::*; +pub(crate) use ecdsa_secp256k1_verify::*; +pub(crate) use ecdsa_secp256r1_recover::*; pub(crate) use ed25519_scalar_mul::*; pub(crate) use read_xdr::*; +#[cfg(feature = "next")] +pub(crate) use sec1_decode_point_compressed::*; diff --git a/soroban-env-host/benches/common/experimental/sec1_decode_point_compressed.rs b/soroban-env-host/benches/common/experimental/sec1_decode_point_compressed.rs new file mode 100644 index 000000000..6657a320b --- /dev/null +++ b/soroban-env-host/benches/common/experimental/sec1_decode_point_compressed.rs @@ -0,0 +1,24 @@ +use crate::common::HostCostMeasurement; +use p256::ecdsa::SigningKey; +use rand::{rngs::StdRng, RngCore}; +use soroban_env_host::{ + cost_runner::{Sec1DecodePointCompressedRun, Sec1DecodePointSample}, + Host, +}; + +pub(crate) struct Sec1DecodePointCompressedMeasure {} + +impl HostCostMeasurement for Sec1DecodePointCompressedMeasure { + type Runner = Sec1DecodePointCompressedRun; + + fn new_random_case(_host: &Host, rng: &mut StdRng, _input: u64) -> Sec1DecodePointSample { + let mut key_bytes = [0u8; 32]; + rng.fill_bytes(&mut key_bytes); + let signer = SigningKey::from_bytes(&key_bytes.into()).unwrap(); + let verifying_key = signer.verifying_key(); + let bytes = verifying_key + .to_encoded_point(true /* compress */) + .to_bytes(); + Sec1DecodePointSample { bytes } + } +} diff --git a/soroban-env-host/benches/common/measure.rs b/soroban-env-host/benches/common/measure.rs index 51f54300b..3fde3a8db 100644 --- a/soroban-env-host/benches/common/measure.rs +++ b/soroban-env-host/benches/common/measure.rs @@ -1,4 +1,4 @@ -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{rngs::StdRng, SeedableRng}; use soroban_bench_utils::{tracking_allocator::AllocationGroupToken, HostTracker}; use soroban_env_host::{ budget::{AsBudget, CostTracker, MeteredCostComponent}, @@ -82,15 +82,22 @@ impl Measurements { where F: Fn(&Measurement) -> u64, { + // data must be preprocessed + assert_eq!( + self.measurements.len(), + self.averaged_net_measurements.len() + ); + use thousands::Separable; let points: Vec<(f32, f32)> = self - .measurements + .averaged_net_measurements .iter() .enumerate() .map(|(i, m)| (i as f32, get_output(m) as f32)) .collect(); let ymin = points.iter().map(|(_, y)| *y).reduce(f32::min).unwrap(); let ymax = points.iter().map(|(_, y)| *y).reduce(f32::max).unwrap(); + let ymean = points.iter().map(|(_, y)| *y).sum::() / points.len().max(1) as f32; if ymin == ymax { return; @@ -98,13 +105,13 @@ impl Measurements { let hist = textplots::utils::histogram(&points, ymin, ymax, 30); let in_min = self - .measurements + .averaged_net_measurements .iter() .map(|m| m.inputs.unwrap_or(0) as f32) .reduce(f32::min) .unwrap(); let in_max = self - .measurements + .averaged_net_measurements .iter() .map(|m| m.inputs.unwrap_or(0) as f32) .reduce(f32::max) @@ -118,11 +125,13 @@ impl Measurements { in_max / in_min.max(1.0) ); println!( - "{} output: min {}; max {}; max/min = {}", + "{} output: min {}; max {}; max/min = {}; mean = {}; count = {}", out_name, ymin.separate_with_commas(), ymax.separate_with_commas(), - ymax / ymin.max(1.0) + ymax / ymin.max(1.0), + ymean.separate_with_commas(), + points.len() ); Chart::new(180, 60, ymin - 100.0, ymax + 100.0) .lineplot(&Shape::Bars(&hist)) @@ -442,19 +451,20 @@ pub fn measure_worst_case_costs( }) } -/// Measure the cost variation of a HCM. `sweep_input` specifies whether the input -/// is fixed or randomized. -/// if true - input is randomized, with `large_input` specifying the upperbound -/// of the input size -/// if false - input size is fixed at `large_input` -/// `iteration` specifies number of iterations to run the measurement -/// `include_best_case` specifies whether best case is included. Often the best case -/// is a trivial case that isn't too relevant (and never hit). So if one is more -/// interested in the worst/average analysis, it might be useful to throw it away. +/// Measure the cost variation of a HCM. +/// +/// - `iteration` specifies number of iterations to run the measurement for +/// - `get_rand_input` is called to generate an input for the `new_random_case` +/// at each iteration +/// - `get_worst_input` gets the input corresponding to the `new_worst_case` +/// - `include_best_case` specifies whether best case is included. Often the +/// best case is a trivial case that isn't too relevant (and never hit). So if +/// one is more interested in the worst/average analysis, set this to `false`. + pub fn measure_cost_variation( - large_input: u64, iterations: u64, - sweep_input: bool, + get_rand_input: fn() -> u64, + get_worst_input: fn() -> u64, include_best_case: bool, ) -> Result { let mut i = 0; @@ -478,21 +488,18 @@ pub fn measure_cost_variation( let measurements = measure_costs_inner::( |host| { i += 1; - let input = if sweep_input { - rng.gen_range(1..=2 + large_input) - } else { - large_input - }; match i { 1 => { if include_best_case { Some(HCM::new_best_case(host, &mut rng)) } else { - Some(HCM::new_random_case(host, &mut rng, input)) + Some(HCM::new_random_case(host, &mut rng, get_rand_input())) } } - 2 => Some(HCM::new_worst_case(host, &mut rng, large_input)), - n if n < iterations => Some(HCM::new_random_case(host, &mut rng, input)), + 2 => Some(HCM::new_worst_case(host, &mut rng, get_worst_input())), + n if n <= iterations => { + Some(HCM::new_random_case(host, &mut rng, get_rand_input())) + } _ => None, } }, diff --git a/soroban-env-host/benches/common/mod.rs b/soroban-env-host/benches/common/mod.rs index 249d88c9e..874586ded 100644 --- a/soroban-env-host/benches/common/mod.rs +++ b/soroban-env-host/benches/common/mod.rs @@ -56,6 +56,10 @@ pub(crate) fn for_each_experimental_cost_measurement( call_bench::(&mut params)?; call_bench::(&mut params)?; call_bench::(&mut params)?; + call_bench::(&mut params)?; + call_bench::(&mut params)?; + call_bench::(&mut params)?; + call_bench::(&mut params)?; Ok(params) } @@ -64,7 +68,9 @@ pub(crate) fn for_each_host_cost_measurement( let mut params: BTreeMap = BTreeMap::new(); - call_bench::(&mut params)?; + #[cfg(not(feature = "next"))] + call_bench::(&mut params)?; + call_bench::(&mut params)?; call_bench::(&mut params)?; call_bench::(&mut params)?; @@ -108,6 +114,10 @@ pub(crate) fn for_each_host_cost_measurement( call_bench::(&mut params)?; call_bench::(&mut params)?; call_bench::(&mut params)?; + + call_bench::(&mut params)?; + call_bench::(&mut params)?; + call_bench::(&mut params)?; } // These three mem ones are derived analytically, we do not calibrate them typically if std::env::var("INCLUDE_ANALYTICAL_COSTTYPES").is_ok() { diff --git a/soroban-env-host/benches/variation_histograms.rs b/soroban-env-host/benches/variation_histograms.rs index a624b741e..d0d4992ce 100644 --- a/soroban-env-host/benches/variation_histograms.rs +++ b/soroban-env-host/benches/variation_histograms.rs @@ -1,24 +1,38 @@ // Run this with -// $ cargo bench --features testutils --bench variation_histograms -- --nocapture +// $ RUN_EXPERIMENT=1 cargo bench --features bench,next --bench variation_histograms -- --nocapture mod common; use common::*; +use rand::{rngs::StdRng, Rng, SeedableRng}; use soroban_env_host::{budget::MeteredCostComponent, cost_runner::CostRunner}; +#[allow(unused)] +fn gen_random_input(lower: u64, upper: u64) -> u64 { + let mut rng = StdRng::from_seed([0xff; 32]); + rng.gen_range(lower..=upper) +} + struct LinearModelTables; impl Benchmark for LinearModelTables { fn bench( ) -> std::io::Result<(MeteredCostComponent, MeteredCostComponent)> { - let mut measurements = measure_cost_variation::(100, 1000, false, false)?; + // the inputs will be ignored if the measurment is for a constant model + let mut measurements = measure_cost_variation::(100_000, || 0, || 0, false)?; measurements.check_range_against_baseline(&HCM::Runner::COST_TYPE)?; measurements.preprocess(); measurements.report_histogram("cpu", |m| m.cpu_insns); measurements.report_histogram("mem", |m| m.mem_bytes); + measurements.report_histogram("time [nsecs]", |m| m.time_nsecs); Ok(Default::default()) } } #[cfg(all(test, any(target_os = "linux", target_os = "macos")))] fn main() -> std::io::Result<()> { - for_each_experimental_cost_measurement::()?; + let _params = if std::env::var("RUN_EXPERIMENT").is_err() { + for_each_host_cost_measurement::()? + } else { + for_each_experimental_cost_measurement::()? + }; + Ok(()) } diff --git a/soroban-env-host/observations/test__crypto__test_secp256r1_signature_verification.json b/soroban-env-host/observations/test__crypto__test_secp256r1_signature_verification.json new file mode 100644 index 000000000..71e4d33d9 --- /dev/null +++ b/soroban-env-host/observations/test__crypto__test_secp256r1_signature_verification.json @@ -0,0 +1,10 @@ +{ + " 0 begin": "cpu:0, mem:0, prngs:-/-, objs:-/-, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-", + " 1 call bytes_new_from_slice(65)": "", + " 2 ret bytes_new_from_slice -> Ok(Bytes(obj#1))": "cpu:977, mem:145, objs:-/1@b2474f93", + " 3 call bytes_new_from_slice(32)": "", + " 4 ret bytes_new_from_slice -> Ok(Bytes(obj#3))": "cpu:1946, mem:257, objs:-/2@6c013500", + " 5 call bytes_new_from_slice(64)": "", + " 6 ret bytes_new_from_slice -> Ok(Bytes(obj#5))": "cpu:2923, mem:401, objs:-/3@2fb55113", + " 7 end": "cpu:0, mem:0, prngs:-/-, objs:-/3@2fb55113, vm:-/-, evt:-, store:-/-, foot:-, stk:-, auth:-/-" +} \ No newline at end of file diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs index ec0c30bfb..213e3a6ba 100644 --- a/soroban-env-host/src/budget.rs +++ b/soroban-env-host/src/budget.rs @@ -84,7 +84,10 @@ impl Default for BudgetTracker { ContractCostType::VmCachedInstantiation => init_input(), // length of the wasm bytes, ContractCostType::InvokeVmFunction => (), ContractCostType::ComputeKeccak256Hash => init_input(), // number of bytes in the buffer + #[cfg(not(feature = "next"))] ContractCostType::ComputeEcdsaSecp256k1Sig => (), + #[cfg(feature = "next")] + ContractCostType::DecodeEcdsaCurve256Sig => (), ContractCostType::RecoverEcdsaSecp256k1Key => (), ContractCostType::Int256AddSub => (), ContractCostType::Int256Mul => (), @@ -133,6 +136,10 @@ impl Default for BudgetTracker { ContractCostType::InstantiateWasmExports => init_input(), #[cfg(feature = "next")] ContractCostType::InstantiateWasmDataSegmentBytes => init_input(), + #[cfg(feature = "next")] + ContractCostType::Sec1DecodePointUncompressed => (), + #[cfg(feature = "next")] + ContractCostType::VerifyEcdsaSecp256r1Sig => (), } } mt @@ -383,10 +390,16 @@ impl Default for BudgetImpl { cpu.const_term = 3766; cpu.lin_term = ScaledU64(5969); } + #[cfg(not(feature = "next"))] ContractCostType::ComputeEcdsaSecp256k1Sig => { cpu.const_term = 710; cpu.lin_term = ScaledU64(0); } + #[cfg(feature = "next")] + ContractCostType::DecodeEcdsaCurve256Sig => { + cpu.const_term = 710; + cpu.lin_term = ScaledU64(0); + } ContractCostType::RecoverEcdsaSecp256k1Key => { cpu.const_term = 2315295; cpu.lin_term = ScaledU64(0); @@ -516,6 +529,16 @@ impl Default for BudgetImpl { cpu.const_term = 0; cpu.lin_term = ScaledU64(14); } + #[cfg(feature = "next")] + ContractCostType::Sec1DecodePointUncompressed => { + cpu.const_term = 1882; + cpu.lin_term = ScaledU64(0); + } + #[cfg(feature = "next")] + ContractCostType::VerifyEcdsaSecp256r1Sig => { + cpu.const_term = 3000906; + cpu.lin_term = ScaledU64(0); + } } // define the memory cost model parameters @@ -593,10 +616,16 @@ impl Default for BudgetImpl { mem.const_term = 0; mem.lin_term = ScaledU64(0); } + #[cfg(not(feature = "next"))] ContractCostType::ComputeEcdsaSecp256k1Sig => { mem.const_term = 0; mem.lin_term = ScaledU64(0); } + #[cfg(feature = "next")] + ContractCostType::DecodeEcdsaCurve256Sig => { + mem.const_term = 0; + mem.lin_term = ScaledU64(0); + } ContractCostType::RecoverEcdsaSecp256k1Key => { mem.const_term = 181; mem.lin_term = ScaledU64(0); @@ -726,6 +755,16 @@ impl Default for BudgetImpl { mem.const_term = 0; mem.lin_term = ScaledU64(126); } + #[cfg(feature = "next")] + ContractCostType::Sec1DecodePointUncompressed => { + mem.const_term = 0; + mem.lin_term = ScaledU64(0); + } + #[cfg(feature = "next")] + ContractCostType::VerifyEcdsaSecp256r1Sig => { + mem.const_term = 0; + mem.lin_term = ScaledU64(0); + } } } @@ -738,7 +777,7 @@ impl Default for BudgetImpl { impl Debug for BudgetImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "{:=<165}", "")?; + writeln!(f, "{:=<175}", "")?; writeln!( f, "Cpu limit: {}; used: {}", @@ -749,10 +788,10 @@ impl Debug for BudgetImpl { "Mem limit: {}; used: {}", self.mem_bytes.limit, self.mem_bytes.total_count )?; - writeln!(f, "{:=<165}", "")?; + writeln!(f, "{:=<175}", "")?; writeln!( f, - "{:<25}{:<15}{:<15}{:<15}{:<15}{:<20}{:<20}{:<20}{:<20}", + "{:<35}{:<15}{:<15}{:<15}{:<15}{:<20}{:<20}{:<20}{:<20}", "CostType", "iterations", "input", @@ -767,7 +806,7 @@ impl Debug for BudgetImpl { let i = ct as usize; writeln!( f, - "{:<25}{:<15}{:<15}{:<15}{:<15}{:<20}{:<20}{:<20}{:<20}", + "{:<35}{:<15}{:<15}{:<15}{:<15}{:<20}{:<20}{:<20}{:<20}", format!("{:?}", ct), self.tracker.cost_tracker[i].iterations, format!("{:?}", self.tracker.cost_tracker[i].inputs), @@ -779,7 +818,7 @@ impl Debug for BudgetImpl { format!("{}", self.mem_bytes.cost_models[i].lin_term), )?; } - writeln!(f, "{:=<165}", "")?; + writeln!(f, "{:=<175}", "")?; writeln!( f, "Internal details (diagnostics info, does not affect fees) " @@ -799,14 +838,14 @@ impl Debug for BudgetImpl { "Shadow mem limit: {}; used: {}", self.mem_bytes.shadow_limit, self.mem_bytes.shadow_total_count )?; - writeln!(f, "{:=<165}", "")?; + writeln!(f, "{:=<175}", "")?; Ok(()) } } impl Display for BudgetImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "{:=<55}", "")?; + writeln!(f, "{:=<65}", "")?; writeln!( f, "Cpu limit: {}; used: {}", @@ -817,23 +856,23 @@ impl Display for BudgetImpl { "Mem limit: {}; used: {}", self.mem_bytes.limit, self.mem_bytes.total_count )?; - writeln!(f, "{:=<55}", "")?; + writeln!(f, "{:=<65}", "")?; writeln!( f, - "{:<25}{:<15}{:<15}", + "{:<35}{:<15}{:<15}", "CostType", "cpu_insns", "mem_bytes", )?; for ct in ContractCostType::variants() { let i = ct as usize; writeln!( f, - "{:<25}{:<15}{:<15}", + "{:<35}{:<15}{:<15}", format!("{:?}", ct), self.tracker.cost_tracker[i].cpu, self.tracker.cost_tracker[i].mem, )?; } - writeln!(f, "{:=<55}", "")?; + writeln!(f, "{:=<65}", "")?; Ok(()) } } @@ -1079,4 +1118,9 @@ impl Budget { pub(crate) fn get_wasmi_fuel_remaining(&self) -> Result { self.0.try_borrow_mut_or_err()?.get_wasmi_fuel_remaining() } + + pub fn reset_default(&self) -> Result<(), HostError> { + *self.0.try_borrow_mut_or_err()? = BudgetImpl::default(); + Ok(()) + } } diff --git a/soroban-env-host/src/budget/util.rs b/soroban-env-host/src/budget/util.rs index 7cec780b0..3c65d4274 100644 --- a/soroban-env-host/src/budget/util.rs +++ b/soroban-env-host/src/budget/util.rs @@ -32,11 +32,6 @@ impl Budget { Ok(self.0.try_borrow_or_err()?.tracker.wasm_memory) } - pub fn reset_default(&self) -> Result<(), HostError> { - *self.0.try_borrow_mut_or_err()? = super::BudgetImpl::default(); - Ok(()) - } - pub fn reset_unlimited(&self) -> Result<(), HostError> { self.reset_unlimited_cpu()?; self.reset_unlimited_mem()?; diff --git a/soroban-env-host/src/cost_runner/cost_types/decode_ecdsa_curve256_sig.rs b/soroban-env-host/src/cost_runner/cost_types/decode_ecdsa_curve256_sig.rs new file mode 100644 index 000000000..abdddccf7 --- /dev/null +++ b/soroban-env-host/src/cost_runner/cost_types/decode_ecdsa_curve256_sig.rs @@ -0,0 +1,49 @@ +use std::hint::black_box; +use std::marker::PhantomData; + +use ecdsa::{PrimeCurve, Signature, SignatureSize}; +use elliptic_curve::CurveArithmetic; +use generic_array::ArrayLength; +// use k256::{ecdsa::Signature, Secp256k1}; + +use crate::cost_runner::{CostRunner, CostType}; +use crate::xdr::ContractCostType::DecodeEcdsaCurve256Sig; + +#[derive(Clone)] +pub struct DecodeEcdsaCurve256SigSample { + pub bytes: Vec, +} + +pub struct DecodeEcdsaCurve256SigRun +where + C: PrimeCurve + CurveArithmetic, + SignatureSize: ArrayLength, +{ + phantom: PhantomData, +} + +impl CostRunner for DecodeEcdsaCurve256SigRun +where + C: PrimeCurve + CurveArithmetic, + SignatureSize: ArrayLength, +{ + const COST_TYPE: CostType = CostType::Contract(DecodeEcdsaCurve256Sig); + + type SampleType = DecodeEcdsaCurve256SigSample; + + type RecycledType = (Option>, DecodeEcdsaCurve256SigSample); + + fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType { + let pk = black_box(host.ecdsa_signature_from_bytes::(&sample.bytes).unwrap()); + black_box((Some(pk), sample)) + } + + fn run_baseline_iter( + host: &crate::Host, + _iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + black_box(host.charge_budget(DecodeEcdsaCurve256Sig, None).unwrap()); + black_box((None, sample)) + } +} diff --git a/soroban-env-host/src/cost_runner/cost_types/mod.rs b/soroban-env-host/src/cost_runner/cost_types/mod.rs index 5adb7cb22..7582ca479 100644 --- a/soroban-env-host/src/cost_runner/cost_types/mod.rs +++ b/soroban-env-host/src/cost_runner/cost_types/mod.rs @@ -1,7 +1,10 @@ +#[cfg(not(feature = "next"))] mod compute_ecdsa_secp256k1_sig; mod compute_ed25519_pubkey; mod compute_keccak256_hash; mod compute_sha256_hash; +#[cfg(feature = "next")] +mod decode_ecdsa_curve256_sig; mod host_mem_alloc; mod host_mem_cmp; mod host_mem_cpy; @@ -9,17 +12,24 @@ mod invoke; mod num_ops; mod prng; mod recover_ecdsa_secp256k1_key; +#[cfg(feature = "next")] +mod sec1_decode_point_uncompressed; mod val_deser; mod val_ser; +#[cfg(feature = "next")] +mod verify_ecdsa_secp256r1_sig; mod verify_ed25519_sig; mod visit_object; mod vm_ops; mod wasm_insn_exec; +#[cfg(not(feature = "next"))] pub use compute_ecdsa_secp256k1_sig::*; pub use compute_ed25519_pubkey::*; pub use compute_keccak256_hash::*; pub use compute_sha256_hash::*; +#[cfg(feature = "next")] +pub use decode_ecdsa_curve256_sig::*; pub use host_mem_alloc::*; pub use host_mem_cmp::*; pub use host_mem_cpy::*; @@ -27,8 +37,12 @@ pub use invoke::*; pub use num_ops::*; pub use prng::*; pub use recover_ecdsa_secp256k1_key::*; +#[cfg(feature = "next")] +pub use sec1_decode_point_uncompressed::*; pub use val_deser::*; pub use val_ser::*; +#[cfg(feature = "next")] +pub use verify_ecdsa_secp256r1_sig::*; pub use verify_ed25519_sig::*; pub use visit_object::*; pub use vm_ops::*; diff --git a/soroban-env-host/src/cost_runner/cost_types/sec1_decode_point_uncompressed.rs b/soroban-env-host/src/cost_runner/cost_types/sec1_decode_point_uncompressed.rs new file mode 100644 index 000000000..55c313128 --- /dev/null +++ b/soroban-env-host/src/cost_runner/cost_types/sec1_decode_point_uncompressed.rs @@ -0,0 +1,42 @@ +use crate::{ + cost_runner::{CostRunner, CostType}, + xdr::ContractCostType::Sec1DecodePointUncompressed, +}; +use p256::ecdsa::VerifyingKey; +use std::hint::black_box; + +pub struct Sec1DecodePointUncompressedRun; + +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct Sec1DecodePointSample { + pub bytes: Box<[u8]>, +} + +impl CostRunner for Sec1DecodePointUncompressedRun { + const COST_TYPE: CostType = CostType::Contract(Sec1DecodePointUncompressed); + + type SampleType = Sec1DecodePointSample; + + type RecycledType = (Self::SampleType, Option); + + fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType { + let vk = black_box( + host.secp256r1_decode_sec1_uncompressed_pubkey(&sample.bytes) + .unwrap(), + ); + black_box((sample, Some(vk))) + } + + fn run_baseline_iter( + host: &crate::Host, + _iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + black_box( + host.charge_budget(Sec1DecodePointUncompressed, None) + .unwrap(), + ); + black_box((sample, None)) + } +} diff --git a/soroban-env-host/src/cost_runner/cost_types/verify_ecdsa_secp256r1_sig.rs b/soroban-env-host/src/cost_runner/cost_types/verify_ecdsa_secp256r1_sig.rs new file mode 100644 index 000000000..d035d7d38 --- /dev/null +++ b/soroban-env-host/src/cost_runner/cost_types/verify_ecdsa_secp256r1_sig.rs @@ -0,0 +1,39 @@ +use crate::{ + cost_runner::{CostRunner, CostType}, + xdr::{ContractCostType::VerifyEcdsaSecp256r1Sig, Hash}, +}; +use p256::ecdsa::{Signature, VerifyingKey}; +use std::hint::black_box; +pub struct VerifyEcdsaSecp256r1SigRun; + +#[derive(Clone)] +pub struct VerifyEcdsaSecp256r1SigSample { + pub pub_key: VerifyingKey, + pub msg_hash: Hash, + pub sig: Signature, +} + +impl CostRunner for VerifyEcdsaSecp256r1SigRun { + const COST_TYPE: CostType = CostType::Contract(VerifyEcdsaSecp256r1Sig); + + type SampleType = VerifyEcdsaSecp256r1SigSample; + + type RecycledType = Self::SampleType; + + fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType { + black_box( + host.secp256r1_verify_signature(&sample.pub_key, &sample.msg_hash, &sample.sig) + .unwrap(), + ); + black_box(sample) + } + + fn run_baseline_iter( + host: &crate::Host, + _iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + black_box(host.charge_budget(VerifyEcdsaSecp256r1Sig, None).unwrap()); + black_box(sample) + } +} diff --git a/soroban-env-host/src/cost_runner/experimental/decode_secp256r1_sig.rs b/soroban-env-host/src/cost_runner/experimental/decode_secp256r1_sig.rs new file mode 100644 index 000000000..3a50a93b1 --- /dev/null +++ b/soroban-env-host/src/cost_runner/experimental/decode_secp256r1_sig.rs @@ -0,0 +1,46 @@ +use crate::{ + budget::CostTracker, + cost_runner::{CostRunner, CostType, DecodeEcdsaCurve256SigRun, DecodeEcdsaCurve256SigSample}, +}; +use p256::NistP256; + +use super::ExperimentalCostType; + +// This experiment verifies that decoding an ECDSA signature with the underlying +// curve being secp256r1 yield similar costs as secp256k1 (currently in use) + +pub struct DecodeSecp256r1SigRun; + +impl CostRunner for DecodeSecp256r1SigRun { + const COST_TYPE: CostType = + CostType::Experimental(ExperimentalCostType::DecodeSecp256r1Signature); + + // we want to capture the output variance w.r.t the random input, thus we + // set `RUN_ITERATIONS` to 1. + const RUN_ITERATIONS: u64 = 1; + + type SampleType = DecodeEcdsaCurve256SigSample; + + type RecycledType = as CostRunner>::RecycledType; + + fn run_iter(host: &crate::Host, iter: u64, sample: Self::SampleType) -> Self::RecycledType { + DecodeEcdsaCurve256SigRun::::run_iter(host, iter, sample) + } + + fn get_tracker(_host: &crate::Host) -> CostTracker { + CostTracker { + iterations: Self::RUN_ITERATIONS, + inputs: None, + cpu: 0, + mem: 0, + } + } + + fn run_baseline_iter( + host: &crate::Host, + iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + DecodeEcdsaCurve256SigRun::run_baseline_iter(host, iter, sample) + } +} diff --git a/soroban-env-host/src/cost_runner/experimental/ecdsa_secp256k1_verify.rs b/soroban-env-host/src/cost_runner/experimental/ecdsa_secp256k1_verify.rs new file mode 100644 index 000000000..091dc78dd --- /dev/null +++ b/soroban-env-host/src/cost_runner/experimental/ecdsa_secp256k1_verify.rs @@ -0,0 +1,56 @@ +use crate::{ + budget::CostTracker, + cost_runner::{CostRunner, CostType}, + xdr::Hash, +}; +use k256::ecdsa::{Signature, VerifyingKey}; +use std::hint::black_box; + +use super::ExperimentalCostType; + +pub struct EcdsaSecp256k1VerifyRun; + +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct EcdsaSecp256k1VerifySample { + pub pub_key: VerifyingKey, + pub msg_hash: Hash, + pub sig: Signature, +} + +impl CostRunner for EcdsaSecp256k1VerifyRun { + const COST_TYPE: CostType = CostType::Experimental(ExperimentalCostType::EcdsaSecp256k1Verify); + + // we want to capture the output variance w.r.t the random input, thus we + // set `RUN_ITERATIONS` to 1. + const RUN_ITERATIONS: u64 = 1; + + type SampleType = EcdsaSecp256k1VerifySample; + + type RecycledType = Self::SampleType; + + fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType { + black_box( + host.ecdsa_secp256k1_verify_signature(&sample.pub_key, &sample.msg_hash, &sample.sig) + .unwrap(), + ); + black_box(sample) + } + + fn get_tracker(_host: &crate::Host) -> CostTracker { + CostTracker { + iterations: Self::RUN_ITERATIONS, + inputs: None, + cpu: 0, + mem: 0, + } + } + + fn run_baseline_iter( + _host: &crate::Host, + _iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + black_box(sample) + } +} diff --git a/soroban-env-host/src/cost_runner/experimental/ecdsa_secp256r1_recover.rs b/soroban-env-host/src/cost_runner/experimental/ecdsa_secp256r1_recover.rs new file mode 100644 index 000000000..7b6f44177 --- /dev/null +++ b/soroban-env-host/src/cost_runner/experimental/ecdsa_secp256r1_recover.rs @@ -0,0 +1,57 @@ +use crate::{ + budget::CostTracker, + cost_runner::{CostRunner, CostType}, + xdr::Hash, +}; +use ecdsa::RecoveryId; +use p256::ecdsa::Signature; +use std::hint::black_box; + +use super::ExperimentalCostType; + +pub struct EcdsaSecp256r1RecoverRun; + +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct EcdsaSecp256r1RecoverSample { + pub msg_hash: Hash, + pub sig: Signature, + pub recovery_id: RecoveryId, +} + +impl CostRunner for EcdsaSecp256r1RecoverRun { + const COST_TYPE: CostType = CostType::Experimental(ExperimentalCostType::EcdsaSecp256r1Recover); + + // we want to capture the output variance w.r.t the random input, thus we + // set `RUN_ITERATIONS` to 1. + const RUN_ITERATIONS: u64 = 1; + + type SampleType = EcdsaSecp256r1RecoverSample; + + type RecycledType = Self::SampleType; + + fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType { + black_box( + host.ecdsa_secp256r1_recover_key(&sample.msg_hash, &sample.sig, sample.recovery_id) + .unwrap(), + ); + black_box(sample) + } + + fn get_tracker(_host: &crate::Host) -> CostTracker { + CostTracker { + iterations: Self::RUN_ITERATIONS, + inputs: None, + cpu: 0, + mem: 0, + } + } + + fn run_baseline_iter( + _host: &crate::Host, + _iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + black_box(sample) + } +} diff --git a/soroban-env-host/src/cost_runner/experimental/mod.rs b/soroban-env-host/src/cost_runner/experimental/mod.rs index 2f0ef2413..e1e202e35 100644 --- a/soroban-env-host/src/cost_runner/experimental/mod.rs +++ b/soroban-env-host/src/cost_runner/experimental/mod.rs @@ -1,8 +1,22 @@ +#[cfg(feature = "next")] +mod decode_secp256r1_sig; +mod ecdsa_secp256k1_verify; +#[cfg(feature = "next")] +mod ecdsa_secp256r1_recover; mod ed25519_scalar_mut; mod read_xdr; +#[cfg(feature = "next")] +mod sec1_decode_point_compressed; +#[cfg(feature = "next")] +pub use decode_secp256r1_sig::*; +pub use ecdsa_secp256k1_verify::*; +#[cfg(feature = "next")] +pub use ecdsa_secp256r1_recover::*; pub use ed25519_scalar_mut::*; pub use read_xdr::*; +#[cfg(feature = "next")] +pub use sec1_decode_point_compressed::*; use crate::xdr::Name; use core::fmt; @@ -11,6 +25,10 @@ use core::fmt; pub enum ExperimentalCostType { EdwardsPointCurve25519ScalarMul, ReadXdrByteArray, + EcdsaSecp256r1Recover, + Sec1DecodePointCompressed, + DecodeSecp256r1Signature, + EcdsaSecp256k1Verify, } impl Name for ExperimentalCostType { @@ -20,6 +38,10 @@ impl Name for ExperimentalCostType { "EdwardsPointCurve25519ScalarMul" } ExperimentalCostType::ReadXdrByteArray => "ReadXdrByteArray", + ExperimentalCostType::EcdsaSecp256r1Recover => "EcdsaSecp256r1Recover", + ExperimentalCostType::Sec1DecodePointCompressed => "Sec1DecodePointCompressed", + ExperimentalCostType::DecodeSecp256r1Signature => "DecodeSecp256r1Signature", + ExperimentalCostType::EcdsaSecp256k1Verify => "EcdsaSecp256k1Verify", } } } diff --git a/soroban-env-host/src/cost_runner/experimental/sec1_decode_point_compressed.rs b/soroban-env-host/src/cost_runner/experimental/sec1_decode_point_compressed.rs new file mode 100644 index 000000000..e5737ed28 --- /dev/null +++ b/soroban-env-host/src/cost_runner/experimental/sec1_decode_point_compressed.rs @@ -0,0 +1,49 @@ +use crate::{ + budget::CostTracker, + cost_runner::{CostRunner, CostType, Sec1DecodePointSample, Sec1DecodePointUncompressedRun}, +}; +use p256::ecdsa::VerifyingKey; + +use super::ExperimentalCostType; + +// This is almost idential to `Sec1DecodePointUncompressedRun` -- the one we are +// officially using, most of methods here are just pass through to its impl. The +// difference in compression methods are in handled in the measurement side, by +// specifing compression method during sample gernation. The only difference +// here is 1. use the experimental cost type 2. use `RUN_ITERATIONS = 1` to +// avoid result-averaging for better statistical analysis +pub struct Sec1DecodePointCompressedRun; + +impl CostRunner for Sec1DecodePointCompressedRun { + const COST_TYPE: CostType = + CostType::Experimental(ExperimentalCostType::Sec1DecodePointCompressed); + + // we want to capture the output variance w.r.t the random input, thus we + // set `RUN_ITERATIONS` to 1. + const RUN_ITERATIONS: u64 = 1; + + type SampleType = Sec1DecodePointSample; + + type RecycledType = (Self::SampleType, Option); + + fn run_iter(host: &crate::Host, iter: u64, sample: Self::SampleType) -> Self::RecycledType { + Sec1DecodePointUncompressedRun::run_iter(host, iter, sample) + } + + fn get_tracker(_host: &crate::Host) -> CostTracker { + CostTracker { + iterations: Self::RUN_ITERATIONS, + inputs: None, + cpu: 0, + mem: 0, + } + } + + fn run_baseline_iter( + host: &crate::Host, + iter: u64, + sample: Self::SampleType, + ) -> Self::RecycledType { + Sec1DecodePointUncompressedRun::run_baseline_iter(host, iter, sample) + } +} diff --git a/soroban-env-host/src/cost_runner/mod.rs b/soroban-env-host/src/cost_runner/mod.rs index 5c741724b..943fd2e15 100644 --- a/soroban-env-host/src/cost_runner/mod.rs +++ b/soroban-env-host/src/cost_runner/mod.rs @@ -2,6 +2,7 @@ mod cost_types; mod experimental; mod runner; +mod util; pub use cost_types::*; pub use experimental::*; diff --git a/soroban-env-host/src/cost_runner/util.rs b/soroban-env-host/src/cost_runner/util.rs new file mode 100644 index 000000000..3895d6508 --- /dev/null +++ b/soroban-env-host/src/cost_runner/util.rs @@ -0,0 +1,51 @@ +use ecdsa::{signature::hazmat::PrehashVerifier, Signature}; +use k256::Secp256k1; + +use crate::{ + xdr::{Hash, ScErrorCode, ScErrorType}, + Host, HostError, +}; + +impl Host { + pub(crate) fn ecdsa_secp256k1_verify_signature( + &self, + verifying_key: &k256::ecdsa::VerifyingKey, + msg_hash: &Hash, + sig: &Signature, + ) -> Result<(), HostError> { + verifying_key + .verify_prehash(msg_hash.as_slice(), sig) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "failed secp256r1 verification", + &[], + ) + }) + } + + #[cfg(feature = "next")] + pub(crate) fn ecdsa_secp256r1_recover_key( + &self, + msg_hash: &Hash, + sig: &Signature, + rid: ecdsa::RecoveryId, + ) -> Result { + let recovered_key = + p256::ecdsa::VerifyingKey::recover_from_prehash(msg_hash.as_slice(), &sig, rid) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "ECDSA-secp256k1 signature recovery failed", + &[], + ) + })?; + Ok(crate::xdr::ScBytes::from(crate::xdr::BytesM::try_from( + recovered_key + .to_encoded_point(/*compress:*/ false) + .as_bytes(), + )?)) + } +} diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index 8dbb4df7e..520f519cf 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -2815,10 +2815,45 @@ impl VmCallerEnv for Host { signature: BytesObject, recovery_id: U32Val, ) -> Result { + #[cfg(not(feature = "next"))] let sig = self.secp256k1_signature_from_bytesobj_input(signature)?; + #[cfg(feature = "next")] + let sig = self.ecdsa_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) + let rk = self.recover_key_ecdsa_secp256k1_internal(&hash, &sig, rid)?; + self.add_host_object(rk) + } + + #[cfg(feature = "next")] + fn verify_sig_ecdsa_secp256r1( + &self, + _vmcaller: &mut VmCaller, + public_key: BytesObject, + msg_digest: BytesObject, + signature: BytesObject, + ) -> Result { + let pk = self.secp256r1_public_key_from_bytesobj_input(public_key)?; + let sig = self.ecdsa_signature_from_bytesobj_input::(signature)?; + let msg_hash = self.hash_from_bytesobj_input("msg_digest", msg_digest)?; + let res = self.secp256r1_verify_signature(&pk, &msg_hash, &sig)?; + Ok(res.into()) + } + + #[cfg(not(feature = "next"))] + fn verify_sig_ecdsa_secp256r1( + &self, + _vmcaller: &mut VmCaller, + _public_key: BytesObject, + _msg_digest: BytesObject, + _signature: BytesObject, + ) -> Result { + Err(self.err( + ScErrorType::Context, + ScErrorCode::InternalError, + "host function `verify_sig_ecdsa_secp256r1` not implemented", + &[], + )) } // endregion: "crypto" module functions diff --git a/soroban-env-host/src/host/crypto.rs b/soroban-env-host/src/host/crypto.rs index 8af99d8d2..3fed7445d 100644 --- a/soroban-env-host/src/host/crypto.rs +++ b/soroban-env-host/src/host/crypto.rs @@ -1,3 +1,4 @@ +use super::metered_clone::MeteredContainer; use crate::host::prng::SEED_BYTES; use crate::{ budget::AsBudget, @@ -5,6 +6,7 @@ use crate::{ xdr::{ContractCostType, Hash, ScBytes, ScErrorCode, ScErrorType}, BytesObject, Error, Host, HostError, U32Val, Val, }; +use elliptic_curve::scalar::IsHigh; use hex_literal::hex; use hmac::{Hmac, Mac}; use rand::RngCore; @@ -12,7 +14,12 @@ use rand_chacha::ChaCha20Rng; use sha2::Sha256; use sha3::Keccak256; -use super::metered_clone::MeteredContainer; +#[cfg(feature = "next")] +use ecdsa::{signature::hazmat::PrehashVerifier, PrimeCurve, Signature, SignatureSize}; +#[cfg(feature = "next")] +use elliptic_curve::CurveArithmetic; +#[cfg(feature = "next")] +use generic_array::ArrayLength; impl Host { // Ed25519 functions @@ -77,13 +84,129 @@ impl Host { }) } + #[cfg(feature = "next")] + pub(crate) fn secp256r1_verify_signature( + &self, + verifying_key: &p256::ecdsa::VerifyingKey, + msg_hash: &Hash, + sig: &Signature, + ) -> Result<(), HostError> { + let _span = tracy_span!("p256 verify"); + self.charge_budget(ContractCostType::VerifyEcdsaSecp256r1Sig, None)?; + verifying_key + .verify_prehash(msg_hash.as_slice(), sig) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "failed secp256r1 verification", + &[], + ) + }) + } + + #[cfg(feature = "next")] + pub(crate) fn secp256r1_decode_sec1_uncompressed_pubkey( + &self, + bytes: &[u8], + ) -> Result { + use sec1::point::Tag; + self.charge_budget(ContractCostType::Sec1DecodePointUncompressed, None)?; + // check and make sure the key was encoded in uncompressed format + let tag = bytes + .first() + .copied() + .ok_or(sec1::Error::PointEncoding) + .and_then(Tag::from_u8) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA public key", + &[], + ) + })?; + if tag != Tag::Uncompressed { + return Err(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA public key", + &[], + )); + } + + p256::ecdsa::VerifyingKey::from_sec1_bytes(bytes).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA public key", + &[], + ) + }) + } + + #[cfg(feature = "next")] + pub(crate) fn secp256r1_public_key_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result { + self.visit_obj(k, |bytes: &ScBytes| { + self.secp256r1_decode_sec1_uncompressed_pubkey(bytes.as_slice()) + }) + } + + // ECDSA functions + #[cfg(feature = "next")] + pub(crate) fn ecdsa_signature_from_bytes( + &self, + bytes: &[u8], + ) -> Result, HostError> + where + C: PrimeCurve + CurveArithmetic, + SignatureSize: ArrayLength, + { + self.charge_budget(ContractCostType::DecodeEcdsaCurve256Sig, None)?; + let sig = Signature::::try_from(bytes).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA sinature", + &[], + ) + })?; + if sig.s().is_high().into() { + Err(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "ECDSA signature 's' part is not normalized to low form", + &[], + )) + } else { + Ok(sig) + } + } + + #[cfg(feature = "next")] + pub(crate) fn ecdsa_signature_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result, HostError> + where + C: PrimeCurve + CurveArithmetic, + SignatureSize: ArrayLength, + { + self.visit_obj(k, |bytes: &ScBytes| { + self.ecdsa_signature_from_bytes(bytes.as_slice()) + }) + } + // ECDSA secp256k1 functions + #[cfg(not(feature = "next"))] pub(crate) fn secp256k1_signature_from_bytes( &self, bytes: &[u8], ) -> Result { - use k256::elliptic_curve::scalar::IsHigh; self.charge_budget(ContractCostType::ComputeEcdsaSecp256k1Sig, None)?; let sig: k256::ecdsa::Signature = k256::ecdsa::Signature::try_from(bytes).map_err(|_| { @@ -106,6 +229,7 @@ impl Host { } } + #[cfg(not(feature = "next"))] pub(crate) fn secp256k1_signature_from_bytesobj_input( &self, k: BytesObject, @@ -145,7 +269,7 @@ impl Host { hash: &Hash, sig: &k256::ecdsa::Signature, rid: k256::ecdsa::RecoveryId, - ) -> Result { + ) -> Result { let _span = tracy_span!("secp256k1 recover"); self.charge_budget(ContractCostType::RecoverEcdsaSecp256k1Key, None)?; let recovered_key = @@ -159,12 +283,11 @@ impl Host { ) }, )?; - let rk = ScBytes::from(crate::xdr::BytesM::try_from( + Ok(ScBytes::from(crate::xdr::BytesM::try_from( recovered_key .to_encoded_point(/*compress:*/ false) .as_bytes(), - )?); - self.add_host_object(rk) + )?)) } // SHA256 functions diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs index 44b529acc..ddb103a9d 100644 --- a/soroban-env-host/src/test/budget_metering.rs +++ b/soroban-env-host/src/test/budget_metering.rs @@ -344,6 +344,7 @@ fn test_metered_collection() -> Result<(), HostError> { fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { let host = Host::default(); + #[cfg(not(feature = "next"))] let tracker: Vec<(u64, Option)> = vec![ (246, None), (1, Some(152)), @@ -371,34 +372,53 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { ]; #[cfg(feature = "next")] - let tracker: Vec<(u64, Option)> = tracker - .into_iter() - .chain( - vec![ - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, None), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, None), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - (1, Some(1)), - ] - .into_iter(), - ) - .collect(); + let tracker: Vec<(u64, Option)> = vec![ + (246, None), + (1, Some(152)), + (1, Some(65)), + (1, Some(74)), + (176, None), + (97, None), + (1, Some(49)), + (1, Some(103)), + (1, Some(193)), + (226, None), + (1, Some(227)), + (1, Some(147)), + (1, Some(147)), + (47, None), + (1, Some(1)), + (1, None), + (1, None), + (1, None), + (1, None), + (1, None), + (1, None), + (1, None), + (1, Some(1)), + (1, Some(1)), /* ParseWasmInstructions*/ + (1, Some(1)), /* ParseWasmFunctions*/ + (1, Some(1)), /* ParseWasmGlobals*/ + (1, Some(1)), /* ParseWasmTableEntries*/ + (1, Some(1)), /* ParseWasmTypes*/ + (1, Some(1)), /* ParseWasmDataSegments*/ + (1, Some(1)), /* ParseWasmElemSegments*/ + (1, Some(1)), /* ParseWasmImports*/ + (1, Some(1)), /* ParseWasmExports*/ + (1, Some(1)), /* ParseWasmDataSegmentBytes*/ + (1, None), /* InstantiateWasmInstructions*/ + (1, Some(1)), /* InstantiateWasmFunctions*/ + (1, Some(1)), /* InstantiateWasmGlobals*/ + (1, Some(1)), /* InstantiateWasmTableEntries*/ + (1, None), /* InstantiateWasmTypes*/ + (1, Some(1)), /* InstantiateWasmDataSegments*/ + (1, Some(1)), /* InstantiateWasmElemSegments*/ + (1, Some(1)), /* InstantiateWasmImports*/ + (1, Some(1)), /* InstantiateWasmExports*/ + (1, Some(1)), /* InstantiateWasmDataSegmentBytes*/ + (1, None), /* Sec1DecodePointUncompressed*/ + (1, None), /* VerifyEcdsaSecp256r1Sig */ + ]; for (ty, &(iterations, input)) in tracker.iter().enumerate() { host.with_budget(|b| b.bulk_charge(ContractCostType::VARIANTS[ty], iterations, input))?; @@ -414,102 +434,102 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { let actual = format!("{:?}", host.as_budget()); #[cfg(not(feature = "next"))] let expected = expect![[r#" - ===================================================================================================================================================================== + =============================================================================================================================================================================== Cpu limit: 100000000; used: 13060190 Mem limit: 41943040; used: 273960 - ===================================================================================================================================================================== - CostType iterations input cpu_insns mem_bytes const_term_cpu lin_term_cpu const_term_mem lin_term_mem - WasmInsnExec 246 None 984 0 4 0 0 0 - MemAlloc 1 Some(152) 453 168 434 16 16 128 - MemCpy 1 Some(65) 50 0 42 16 0 0 - MemCmp 1 Some(74) 53 0 44 16 0 0 - DispatchHostFunction 176 None 54560 0 310 0 0 0 - VisitObject 97 None 5917 0 61 0 0 0 - ValSer 1 Some(49) 241 389 230 29 242 384 - ValDeser 1 Some(103) 62271 309 59052 4001 0 384 - ComputeSha256Hash 1 Some(193) 14310 0 3738 7012 0 0 - ComputeEd25519PubKey 226 None 9097178 0 40253 0 0 0 - VerifyEd25519Sig 1 Some(227) 384738 0 377524 4068 0 0 - VmInstantiation 1 Some(147) 503770 135880 451626 45405 130065 5064 - VmCachedInstantiation 1 Some(147) 503770 135880 451626 45405 130065 5064 - InvokeVmFunction 47 None 91556 658 1948 0 14 0 - ComputeKeccak256Hash 1 Some(1) 3812 0 3766 5969 0 0 - ComputeEcdsaSecp256k1Sig 1 None 710 0 710 0 0 0 - RecoverEcdsaSecp256k1Key 1 None 2315295 181 2315295 0 181 0 - Int256AddSub 1 None 4404 99 4404 0 99 0 - Int256Mul 1 None 4947 99 4947 0 99 0 - Int256Div 1 None 4911 99 4911 0 99 0 - Int256Pow 1 None 4286 99 4286 0 99 0 - Int256Shift 1 None 913 99 913 0 99 0 - ChaCha20DrawBytes 1 Some(1) 1061 0 1058 501 0 0 - ===================================================================================================================================================================== + =============================================================================================================================================================================== + CostType iterations input cpu_insns mem_bytes const_term_cpu lin_term_cpu const_term_mem lin_term_mem + WasmInsnExec 246 None 984 0 4 0 0 0 + MemAlloc 1 Some(152) 453 168 434 16 16 128 + MemCpy 1 Some(65) 50 0 42 16 0 0 + MemCmp 1 Some(74) 53 0 44 16 0 0 + DispatchHostFunction 176 None 54560 0 310 0 0 0 + VisitObject 97 None 5917 0 61 0 0 0 + ValSer 1 Some(49) 241 389 230 29 242 384 + ValDeser 1 Some(103) 62271 309 59052 4001 0 384 + ComputeSha256Hash 1 Some(193) 14310 0 3738 7012 0 0 + ComputeEd25519PubKey 226 None 9097178 0 40253 0 0 0 + VerifyEd25519Sig 1 Some(227) 384738 0 377524 4068 0 0 + VmInstantiation 1 Some(147) 503770 135880 451626 45405 130065 5064 + VmCachedInstantiation 1 Some(147) 503770 135880 451626 45405 130065 5064 + InvokeVmFunction 47 None 91556 658 1948 0 14 0 + ComputeKeccak256Hash 1 Some(1) 3812 0 3766 5969 0 0 + ComputeEcdsaSecp256k1Sig 1 None 710 0 710 0 0 0 + RecoverEcdsaSecp256k1Key 1 None 2315295 181 2315295 0 181 0 + Int256AddSub 1 None 4404 99 4404 0 99 0 + Int256Mul 1 None 4947 99 4947 0 99 0 + Int256Div 1 None 4911 99 4911 0 99 0 + Int256Pow 1 None 4286 99 4286 0 99 0 + Int256Shift 1 None 913 99 913 0 99 0 + ChaCha20DrawBytes 1 Some(1) 1061 0 1058 501 0 0 + =============================================================================================================================================================================== Internal details (diagnostics info, does not affect fees) Total # times meter was called: 23 Shadow cpu limit: 100000000; used: 13060190 Shadow mem limit: 41943040; used: 273960 - ===================================================================================================================================================================== + =============================================================================================================================================================================== "#]]; #[cfg(feature = "next")] - let expected = expect![ - r#" - ===================================================================================================================================================================== - Cpu limit: 100000000; used: 12751453 + let expected = expect![[r#" + =============================================================================================================================================================================== + Cpu limit: 100000000; used: 15754241 Mem limit: 41943040; used: 302115 - ===================================================================================================================================================================== - CostType iterations input cpu_insns mem_bytes const_term_cpu lin_term_cpu const_term_mem lin_term_mem - WasmInsnExec 246 None 984 0 4 0 0 0 - MemAlloc 1 Some(152) 453 168 434 16 16 128 - MemCpy 1 Some(65) 50 0 42 16 0 0 - MemCmp 1 Some(74) 53 0 44 16 0 0 - DispatchHostFunction 176 None 54560 0 310 0 0 0 - VisitObject 97 None 5917 0 61 0 0 0 - ValSer 1 Some(49) 241 389 230 29 242 384 - ValDeser 1 Some(103) 62271 309 59052 4001 0 384 - ComputeSha256Hash 1 Some(193) 14310 0 3738 7012 0 0 - ComputeEd25519PubKey 226 None 9097178 0 40253 0 0 0 - VerifyEd25519Sig 1 Some(227) 384738 0 377524 4068 0 0 - VmInstantiation 1 Some(147) 503770 135880 451626 45405 130065 5064 - VmCachedInstantiation 1 Some(147) 41870 70869 41142 634 69472 1217 - InvokeVmFunction 47 None 91556 658 1948 0 14 0 - ComputeKeccak256Hash 1 Some(1) 3812 0 3766 5969 0 0 - ComputeEcdsaSecp256k1Sig 1 None 710 0 710 0 0 0 - RecoverEcdsaSecp256k1Key 1 None 2315295 181 2315295 0 181 0 - Int256AddSub 1 None 4404 99 4404 0 99 0 - Int256Mul 1 None 4947 99 4947 0 99 0 - Int256Div 1 None 4911 99 4911 0 99 0 - Int256Pow 1 None 4286 99 4286 0 99 0 - Int256Shift 1 None 913 99 913 0 99 0 - ChaCha20DrawBytes 1 Some(1) 1061 0 1058 501 0 0 - ParseWasmInstructions 1 Some(1) 73275 17614 73077 25410 17564 6457 - ParseWasmFunctions 1 Some(1) 4224 370 0 540752 0 47464 - ParseWasmGlobals 1 Some(1) 1377 104 0 176363 0 13420 - ParseWasmTableEntries 1 Some(1) 234 49 0 29989 0 6285 - ParseWasmTypes 1 Some(1) 8292 505 0 1061449 0 64670 - ParseWasmDataSegments 1 Some(1) 1854 227 0 237336 0 29074 - ParseWasmElemSegments 1 Some(1) 2566 375 0 328476 0 48095 - ParseWasmImports 1 Some(1) 5483 806 0 701845 0 103229 - ParseWasmExports 1 Some(1) 3354 284 0 429383 0 36394 - ParseWasmDataSegmentBytes1 Some(1) 0 2 0 28 0 257 - InstantiateWasmInstructions1 None 43030 70704 43030 0 70704 0 - InstantiateWasmFunctions 1 Some(1) 59 114 0 7556 0 14613 - InstantiateWasmGlobals 1 Some(1) 83 53 0 10711 0 6833 - InstantiateWasmTableEntries1 Some(1) 25 8 0 3300 0 1025 - InstantiateWasmTypes 1 None 0 0 0 0 0 0 - InstantiateWasmDataSegments1 Some(1) 179 1012 0 23038 0 129632 - InstantiateWasmElemSegments1 Some(1) 331 106 0 42488 0 13665 - InstantiateWasmImports 1 Some(1) 6476 762 0 828974 0 97637 - InstantiateWasmExports 1 Some(1) 2321 71 0 297100 0 9176 - InstantiateWasmDataSegmentBytes1 Some(1) 0 0 0 14 0 126 - ===================================================================================================================================================================== + =============================================================================================================================================================================== + CostType iterations input cpu_insns mem_bytes const_term_cpu lin_term_cpu const_term_mem lin_term_mem + WasmInsnExec 246 None 984 0 4 0 0 0 + MemAlloc 1 Some(152) 453 168 434 16 16 128 + MemCpy 1 Some(65) 50 0 42 16 0 0 + MemCmp 1 Some(74) 53 0 44 16 0 0 + DispatchHostFunction 176 None 54560 0 310 0 0 0 + VisitObject 97 None 5917 0 61 0 0 0 + ValSer 1 Some(49) 241 389 230 29 242 384 + ValDeser 1 Some(103) 62271 309 59052 4001 0 384 + ComputeSha256Hash 1 Some(193) 14310 0 3738 7012 0 0 + ComputeEd25519PubKey 226 None 9097178 0 40253 0 0 0 + VerifyEd25519Sig 1 Some(227) 384738 0 377524 4068 0 0 + VmInstantiation 1 Some(147) 503770 135880 451626 45405 130065 5064 + VmCachedInstantiation 1 Some(147) 41870 70869 41142 634 69472 1217 + InvokeVmFunction 47 None 91556 658 1948 0 14 0 + ComputeKeccak256Hash 1 Some(1) 3812 0 3766 5969 0 0 + DecodeEcdsaCurve256Sig 1 None 710 0 710 0 0 0 + RecoverEcdsaSecp256k1Key 1 None 2315295 181 2315295 0 181 0 + Int256AddSub 1 None 4404 99 4404 0 99 0 + Int256Mul 1 None 4947 99 4947 0 99 0 + Int256Div 1 None 4911 99 4911 0 99 0 + Int256Pow 1 None 4286 99 4286 0 99 0 + Int256Shift 1 None 913 99 913 0 99 0 + ChaCha20DrawBytes 1 Some(1) 1061 0 1058 501 0 0 + ParseWasmInstructions 1 Some(1) 73275 17614 73077 25410 17564 6457 + ParseWasmFunctions 1 Some(1) 4224 370 0 540752 0 47464 + ParseWasmGlobals 1 Some(1) 1377 104 0 176363 0 13420 + ParseWasmTableEntries 1 Some(1) 234 49 0 29989 0 6285 + ParseWasmTypes 1 Some(1) 8292 505 0 1061449 0 64670 + ParseWasmDataSegments 1 Some(1) 1854 227 0 237336 0 29074 + ParseWasmElemSegments 1 Some(1) 2566 375 0 328476 0 48095 + ParseWasmImports 1 Some(1) 5483 806 0 701845 0 103229 + ParseWasmExports 1 Some(1) 3354 284 0 429383 0 36394 + ParseWasmDataSegmentBytes 1 Some(1) 0 2 0 28 0 257 + InstantiateWasmInstructions 1 None 43030 70704 43030 0 70704 0 + InstantiateWasmFunctions 1 Some(1) 59 114 0 7556 0 14613 + InstantiateWasmGlobals 1 Some(1) 83 53 0 10711 0 6833 + InstantiateWasmTableEntries 1 Some(1) 25 8 0 3300 0 1025 + InstantiateWasmTypes 1 None 0 0 0 0 0 0 + InstantiateWasmDataSegments 1 Some(1) 179 1012 0 23038 0 129632 + InstantiateWasmElemSegments 1 Some(1) 331 106 0 42488 0 13665 + InstantiateWasmImports 1 Some(1) 6476 762 0 828974 0 97637 + InstantiateWasmExports 1 Some(1) 2321 71 0 297100 0 9176 + InstantiateWasmDataSegmentBytes 1 Some(1) 0 0 0 14 0 126 + Sec1DecodePointUncompressed 1 None 1882 0 1882 0 0 0 + VerifyEcdsaSecp256r1Sig 1 None 3000906 0 3000906 0 0 0 + =============================================================================================================================================================================== Internal details (diagnostics info, does not affect fees) - Total # times meter was called: 43 - Shadow cpu limit: 100000000; used: 12751453 + Total # times meter was called: 45 + Shadow cpu limit: 100000000; used: 15754241 Shadow mem limit: 41943040; used: 302115 - ===================================================================================================================================================================== + =============================================================================================================================================================================== - "# - ]; + "#]]; expected.assert_eq(&actual); assert_eq!( diff --git a/soroban-env-host/src/test/crypto.rs b/soroban-env-host/src/test/crypto.rs index 616f0905e..504f5e47c 100644 --- a/soroban-env-host/src/test/crypto.rs +++ b/soroban-env-host/src/test/crypto.rs @@ -1,7 +1,6 @@ use crate::xdr::{ScErrorCode, ScErrorType}; -use crate::{Env, Host, HostError}; +use crate::{Env, EnvBase, Host, HostError, U32Val}; use hex::ToHex; -use soroban_env_common::{EnvBase, U32Val}; fn is_budget_exceeded(err: HostError) -> bool { err.error.is_type(ScErrorType::Budget) && err.error.is_code(ScErrorCode::ExceededLimit) @@ -255,3 +254,177 @@ fn recover_ecdsa_secp256k1_key_test() { ) .err().unwrap())); } + +#[cfg(feature = "next")] +#[test] +fn test_secp256r1_signature_verification() -> Result<(), HostError> { + use crate::{VmCaller, VmCallerEnv}; + + let host = observe_host!(Host::default()); + + let verify_sig = |public_key: Vec, + msg_digest: Vec, + signature: Vec| + -> Result { + let public_key_obj = host.bytes_new_from_slice(&public_key)?; + let msg_digest_obj = host.bytes_new_from_slice(&msg_digest)?; + let signature_obj = host.bytes_new_from_slice(&signature)?; + // Make sure we always verify with the fresh budget to make large payload tests + // independent of each other. + host.budget_ref().reset_default().unwrap(); + ::verify_sig_ecdsa_secp256r1( + &host, + &mut VmCaller::none(), + public_key_obj, + msg_digest_obj, + signature_obj, + ) + }; + + // 0. Valid + assert!(verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest, computed from sha256(e1130af../* see NIST for full message */) */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ).is_ok()); + + // 1. invalid message digest + // a) delete one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a").unwrap() /* msg digest, deleted one byte */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err( + res, + (ScErrorType::Object, ScErrorCode::UnexpectedSize), /* this error comes from `xdr::Hash::try_from` */ + ); + // b) append one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a9400").unwrap() /* msg digest, padded one extra byte */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Object, ScErrorCode::UnexpectedSize)); + // c) modify one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a95").unwrap() /* msg digest, padded one extra byte */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + + // 2. invalid public key + // a) delete one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee9").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // b) append one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee92700").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // c) modify one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee928").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // d) wrong compression format: compressed, y is even (tag = 0x02) + let res = verify_sig( + hex::decode("02e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // e) wrong compression format: compressed, y is odd (tag = 0x03) + let res = verify_sig( + hex::decode("03e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // f) wrong compression format: y is zero element (tag = 0x00) + let res = verify_sig( + hex::decode("00").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // g) invalid tag + let res = verify_sig( + hex::decode("09e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // h) correct compression, invalid point (flip y and x coordinates) + let res = verify_sig( + hex::decode("04970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + + // 3. signature + // a) delete one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec87" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // b) append one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c00" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // c) modify one byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871d" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // d) r is 0 + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("000000000000000000000000000000000000000000000000000000000000000017c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // e) s is 0 + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f0000000000000000000000000000000000000000000000000000000000000000" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // f) s is in the upper half + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + /* this signature is produced by inverting the `s` of the signature + above, thus it is still a mathematically correct one, but since it's not + normalized it will get rejected by us */ + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4fe83aaf697e6f763e1fc4632bea5420ed7898c87d313e0f5361ae2cb3a4769e35" ).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + // g) invalid signature, change a byte + let res = verify_sig( + hex::decode("04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927").unwrap() /* public key */, + hex::decode("d1b8ef21eb4182ee270638061063a3f3c16c114e33937f69fb232cc833965a94").unwrap() /* msg digest */, + hex::decode("bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871d" /* signature */).unwrap() + ); + HostError::result_matches_err(res, (ScErrorType::Crypto, ScErrorCode::InvalidInput)); + + Ok(()) +} diff --git a/soroban-env-host/src/test/hostile.rs b/soroban-env-host/src/test/hostile.rs index 5a98deabb..09ab71e17 100644 --- a/soroban-env-host/src/test/hostile.rs +++ b/soroban-env-host/src/test/hostile.rs @@ -532,89 +532,91 @@ fn excessive_logging() -> Result<(), HostError> { #[cfg(feature = "next")] let expected_budget = expect![[r#" - ======================================================= + ================================================================= Cpu limit: 2000000; used: 212908 Mem limit: 500000; used: 166460 - ======================================================= - CostType cpu_insns mem_bytes - WasmInsnExec 300 0 - MemAlloc 15292 67040 - MemCpy 2331 0 - MemCmp 416 0 - DispatchHostFunction 310 0 - VisitObject 244 0 - ValSer 0 0 - ValDeser 0 0 - ComputeSha256Hash 3738 0 - ComputeEd25519PubKey 0 0 - VerifyEd25519Sig 0 0 - VmInstantiation 0 0 - VmCachedInstantiation 0 0 - InvokeVmFunction 1948 14 - ComputeKeccak256Hash 0 0 - ComputeEcdsaSecp256k1Sig 0 0 - RecoverEcdsaSecp256k1Key 0 0 - Int256AddSub 0 0 - Int256Mul 0 0 - Int256Div 0 0 - Int256Pow 0 0 - Int256Shift 0 0 - ChaCha20DrawBytes 0 0 - ParseWasmInstructions 74665 17967 - ParseWasmFunctions 4224 370 - ParseWasmGlobals 1377 104 - ParseWasmTableEntries 29989 6285 - ParseWasmTypes 8292 505 - ParseWasmDataSegments 0 0 - ParseWasmElemSegments 0 0 - ParseWasmImports 5483 806 - ParseWasmExports 6709 568 - ParseWasmDataSegmentBytes0 0 - InstantiateWasmInstructions43030 70704 - InstantiateWasmFunctions 59 114 - InstantiateWasmGlobals 83 53 - InstantiateWasmTableEntries3300 1025 - InstantiateWasmTypes 0 0 - InstantiateWasmDataSegments0 0 - InstantiateWasmElemSegments0 0 - InstantiateWasmImports 6476 762 - InstantiateWasmExports 4642 143 - InstantiateWasmDataSegmentBytes0 0 - ======================================================= + ================================================================= + CostType cpu_insns mem_bytes + WasmInsnExec 300 0 + MemAlloc 15292 67040 + MemCpy 2331 0 + MemCmp 416 0 + DispatchHostFunction 310 0 + VisitObject 244 0 + ValSer 0 0 + ValDeser 0 0 + ComputeSha256Hash 3738 0 + ComputeEd25519PubKey 0 0 + VerifyEd25519Sig 0 0 + VmInstantiation 0 0 + VmCachedInstantiation 0 0 + InvokeVmFunction 1948 14 + ComputeKeccak256Hash 0 0 + DecodeEcdsaCurve256Sig 0 0 + RecoverEcdsaSecp256k1Key 0 0 + Int256AddSub 0 0 + Int256Mul 0 0 + Int256Div 0 0 + Int256Pow 0 0 + Int256Shift 0 0 + ChaCha20DrawBytes 0 0 + ParseWasmInstructions 74665 17967 + ParseWasmFunctions 4224 370 + ParseWasmGlobals 1377 104 + ParseWasmTableEntries 29989 6285 + ParseWasmTypes 8292 505 + ParseWasmDataSegments 0 0 + ParseWasmElemSegments 0 0 + ParseWasmImports 5483 806 + ParseWasmExports 6709 568 + ParseWasmDataSegmentBytes 0 0 + InstantiateWasmInstructions 43030 70704 + InstantiateWasmFunctions 59 114 + InstantiateWasmGlobals 83 53 + InstantiateWasmTableEntries 3300 1025 + InstantiateWasmTypes 0 0 + InstantiateWasmDataSegments 0 0 + InstantiateWasmElemSegments 0 0 + InstantiateWasmImports 6476 762 + InstantiateWasmExports 4642 143 + InstantiateWasmDataSegmentBytes 0 0 + Sec1DecodePointUncompressed 0 0 + VerifyEcdsaSecp256r1Sig 0 0 + ================================================================= "#]]; #[cfg(not(feature = "next"))] let expected_budget = expect![[r#" - ======================================================= + ================================================================= Cpu limit: 2000000; used: 522315 Mem limit: 500000; used: 202391 - ======================================================= - CostType cpu_insns mem_bytes - WasmInsnExec 300 0 - MemAlloc 15750 67248 - MemCpy 2298 0 - MemCmp 696 0 - DispatchHostFunction 310 0 - VisitObject 244 0 - ValSer 0 0 - ValDeser 0 0 - ComputeSha256Hash 3738 0 - ComputeEd25519PubKey 0 0 - VerifyEd25519Sig 0 0 - VmInstantiation 497031 135129 - VmCachedInstantiation 0 0 - InvokeVmFunction 1948 14 - ComputeKeccak256Hash 0 0 - ComputeEcdsaSecp256k1Sig 0 0 - RecoverEcdsaSecp256k1Key 0 0 - Int256AddSub 0 0 - Int256Mul 0 0 - Int256Div 0 0 - Int256Pow 0 0 - Int256Shift 0 0 - ChaCha20DrawBytes 0 0 - ======================================================= + ================================================================= + CostType cpu_insns mem_bytes + WasmInsnExec 300 0 + MemAlloc 15750 67248 + MemCpy 2298 0 + MemCmp 696 0 + DispatchHostFunction 310 0 + VisitObject 244 0 + ValSer 0 0 + ValDeser 0 0 + ComputeSha256Hash 3738 0 + ComputeEd25519PubKey 0 0 + VerifyEd25519Sig 0 0 + VmInstantiation 497031 135129 + VmCachedInstantiation 0 0 + InvokeVmFunction 1948 14 + ComputeKeccak256Hash 0 0 + ComputeEcdsaSecp256k1Sig 0 0 + RecoverEcdsaSecp256k1Key 0 0 + Int256AddSub 0 0 + Int256Mul 0 0 + Int256Div 0 0 + Int256Pow 0 0 + Int256Shift 0 0 + ChaCha20DrawBytes 0 0 + ================================================================= "#]]; diff --git a/soroban-env-host/src/testutils.rs b/soroban-env-host/src/testutils.rs index 435a96968..d377734c4 100644 --- a/soroban-env-host/src/testutils.rs +++ b/soroban-env-host/src/testutils.rs @@ -909,7 +909,6 @@ pub(crate) mod wasm { let mut fe = me.func(Arity(0), 0); fe.push(Symbol::try_from_small_str("pass").unwrap()); let (mut me, fid) = fe.finish(); - println!("{}", fid.0); // exporting a function I defined is okay me.export_func(fid, "test"); // exporting an imported function is also okay, although weird diff --git a/soroban-env-host/tests/secp256r1_sig_ver.rs b/soroban-env-host/tests/secp256r1_sig_ver.rs new file mode 100644 index 000000000..ab63f937a --- /dev/null +++ b/soroban-env-host/tests/secp256r1_sig_ver.rs @@ -0,0 +1,243 @@ +#[cfg(feature = "next")] +mod v21 { + use elliptic_curve::sec1::FromEncodedPoint; + use generic_array::GenericArray; + use p256::{ + ecdsa::{Signature, VerifyingKey}, + AffinePoint, EncodedPoint, + }; + use soroban_env_common::EnvBase; + use soroban_env_host::{budget::AsBudget, Host, HostError, VmCaller, VmCallerEnv}; + + // Test vectors copied from + // https://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3ecdsatestvectors.zip + // `SigVer.rsp` section [P-256,SHA-256] + #[test] + fn test_secp256r1_sig_ver_with_sha256_on_msg() -> Result<(), HostError> { + let host = Host::default(); + // we use call the host function via the `VmCaller` interface to get through + // protocol gating, since the secp256r1 host function is added in v21 + let mut vmcaller = VmCaller::none(); + for test_vector in FIPS186_ECDSA_TEST_VECTORS { + // reset the budget for each test case so they don't interfere + host.as_budget().reset_default()?; + let msg = host.bytes_new_from_slice(&hex::decode(test_vector.msg).unwrap())?; + let msg_hash = host.compute_hash_sha256(&mut vmcaller, msg)?; + let verifier = VerifyingKey::from_affine( + AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates( + &GenericArray::from_slice(hex::decode(test_vector.qx).unwrap().as_slice()), + &GenericArray::from_slice(hex::decode(test_vector.qy).unwrap().as_slice()), + false, + )) + .unwrap(), + ) + .unwrap(); + let public_key = host.bytes_new_from_slice(&verifier.to_sec1_bytes())?; + let mut sig = Signature::from_scalars( + GenericArray::clone_from_slice(hex::decode(test_vector.r).unwrap().as_slice()), + GenericArray::clone_from_slice(hex::decode(test_vector.s).unwrap().as_slice()), + ) + .unwrap(); + // NIST test cases do not reject low S but we do, so here we normalize it + sig = sig.normalize_s().unwrap_or(sig); + let sig_obj = host.bytes_new_from_slice(&sig.to_bytes())?; + + let res = host.verify_sig_ecdsa_secp256r1(&mut vmcaller, public_key, msg_hash, sig_obj); + + if test_vector.result == &SigVerResult::Ok { + assert!(res.is_ok()) + } else { + assert!(res.is_err()) + } + } + Ok(()) + } + + #[derive(Eq, PartialEq, Debug)] + enum SigVerResult { + Ok = 0, + MsgChanged = 1, + RChanged = 2, + SChanged = 3, + QChanged = 4, + } + + struct Fips186EcdsaTestVector { + msg: &'static str, + qx: &'static str, + qy: &'static str, + r: &'static str, + s: &'static str, + result: &'static SigVerResult, + } + + const FIPS186_ECDSA_TEST_VECTORS: &[Fips186EcdsaTestVector; 15] = &[ + Fips186EcdsaTestVector { + msg: "e4796db5f785f207aa30d311693b3702821dff1168fd2e04c0836825aefd850d9aa60326d88cde1a23c7745351392ca2288d632c264f197d05cd424a30336c19fd09bb229654f0222fcb881a4b35c290a093ac159ce13409111ff0358411133c24f5b8e2090d6db6558afc36f06ca1f6ef779785adba68db27a409859fc4c4a0", + qx: "87f8f2b218f49845f6f10eec3877136269f5c1a54736dbdf69f89940cad41555", + qy: "e15f369036f49842fac7a86c8a2b0557609776814448b8f5e84aa9f4395205e9", + r: "d19ff48b324915576416097d2544f7cbdf8768b1454ad20e0baac50e211f23b0", + s: "a3e81e59311cdfff2d4784949f7a2cb50ba6c3a91fa54710568e61aca3e847c6", + result: &SigVerResult::SChanged + }, + Fips186EcdsaTestVector { + msg: "069a6e6b93dfee6df6ef6997cd80dd2182c36653cef10c655d524585655462d683877f95ecc6d6c81623d8fac4e900ed0019964094e7de91f1481989ae1873004565789cbf5dc56c62aedc63f62f3b894c9c6f7788c8ecaadc9bd0e81ad91b2b3569ea12260e93924fdddd3972af5273198f5efda0746219475017557616170e", + qx: "5cf02a00d205bdfee2016f7421807fc38ae69e6b7ccd064ee689fc1a94a9f7d2", + qy: "ec530ce3cc5c9d1af463f264d685afe2b4db4b5828d7e61b748930f3ce622a85", + r: "dc23d130c6117fb5751201455e99f36f59aba1a6a21cf2d0e7481a97451d6693", + s: "d6ce7708c18dbf35d4f8aa7240922dc6823f2e7058cbc1484fcad1599db5018c", + result: &SigVerResult::RChanged + }, + Fips186EcdsaTestVector { + msg: "df04a346cf4d0e331a6db78cca2d456d31b0a000aa51441defdb97bbeb20b94d8d746429a393ba88840d661615e07def615a342abedfa4ce912e562af714959896858af817317a840dcff85a057bb91a3c2bf90105500362754a6dd321cdd86128cfc5f04667b57aa78c112411e42da304f1012d48cd6a7052d7de44ebcc01de", + qx: "2ddfd145767883ffbb0ac003ab4a44346d08fa2570b3120dcce94562422244cb", + qy: "5f70c7d11ac2b7a435ccfbbae02c3df1ea6b532cc0e9db74f93fffca7c6f9a64", + r: "9913111cff6f20c5bf453a99cd2c2019a4e749a49724a08774d14e4c113edda8", + s: "9467cd4cd21ecb56b0cab0a9a453b43386845459127a952421f5c6382866c5cc", + result: &SigVerResult::QChanged + }, + Fips186EcdsaTestVector { + msg: "e1130af6a38ccb412a9c8d13e15dbfc9e69a16385af3c3f1e5da954fd5e7c45fd75e2b8c36699228e92840c0562fbf3772f07e17f1add56588dd45f7450e1217ad239922dd9c32695dc71ff2424ca0dec1321aa47064a044b7fe3c2b97d03ce470a592304c5ef21eed9f93da56bb232d1eeb0035f9bf0dfafdcc4606272b20a3", + qx: "e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c", + qy: "970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927", + r: "bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f", + s: "17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c", + result: &SigVerResult::Ok + }, + Fips186EcdsaTestVector { + msg: "73c5f6a67456ae48209b5f85d1e7de7758bf235300c6ae2bdceb1dcb27a7730fb68c950b7fcada0ecc4661d3578230f225a875e69aaa17f1e71c6be5c831f22663bac63d0c7a9635edb0043ff8c6f26470f02a7bc56556f1437f06dfa27b487a6c4290d8bad38d4879b334e341ba092dde4e4ae694a9c09302e2dbf443581c08", + qx: "e0fc6a6f50e1c57475673ee54e3a57f9a49f3328e743bf52f335e3eeaa3d2864", + qy: "7f59d689c91e463607d9194d99faf316e25432870816dde63f5d4b373f12f22a", + r: "1d75830cd36f4c9aa181b2c4221e87f176b7f05b7c87824e82e396c88315c407", + s: "cb2acb01dac96efc53a32d4a0d85d0c2e48955214783ecf50a4f0414a319c05a", + result: &SigVerResult::Ok + }, + Fips186EcdsaTestVector { + msg: "666036d9b4a2426ed6585a4e0fd931a8761451d29ab04bd7dc6d0c5b9e38e6c2b263ff6cb837bd04399de3d757c6c7005f6d7a987063cf6d7e8cb38a4bf0d74a282572bd01d0f41e3fd066e3021575f0fa04f27b700d5b7ddddf50965993c3f9c7118ed78888da7cb221849b3260592b8e632d7c51e935a0ceae15207bedd548", + qx: "a849bef575cac3c6920fbce675c3b787136209f855de19ffe2e8d29b31a5ad86", + qy: "bf5fe4f7858f9b805bd8dcc05ad5e7fb889de2f822f3d8b41694e6c55c16b471", + r: "25acc3aa9d9e84c7abf08f73fa4195acc506491d6fc37cb9074528a7db87b9d6", + s: "9b21d5b5259ed3f2ef07dfec6cc90d3a37855d1ce122a85ba6a333f307d31537", + result: &SigVerResult::RChanged + }, + Fips186EcdsaTestVector { + msg: "7e80436bce57339ce8da1b5660149a20240b146d108deef3ec5da4ae256f8f894edcbbc57b34ce37089c0daa17f0c46cd82b5a1599314fd79d2fd2f446bd5a25b8e32fcf05b76d644573a6df4ad1dfea707b479d97237a346f1ec632ea5660efb57e8717a8628d7f82af50a4e84b11f21bdff6839196a880ae20b2a0918d58cd", + qx: "3dfb6f40f2471b29b77fdccba72d37c21bba019efa40c1c8f91ec405d7dcc5df", + qy: "f22f953f1e395a52ead7f3ae3fc47451b438117b1e04d613bc8555b7d6e6d1bb", + r: "548886278e5ec26bed811dbb72db1e154b6f17be70deb1b210107decb1ec2a5a", + s: "e93bfebd2f14f3d827ca32b464be6e69187f5edbd52def4f96599c37d58eee75", + result: &SigVerResult::QChanged + }, + Fips186EcdsaTestVector { + msg: "1669bfb657fdc62c3ddd63269787fc1c969f1850fb04c933dda063ef74a56ce13e3a649700820f0061efabf849a85d474326c8a541d99830eea8131eaea584f22d88c353965dabcdc4bf6b55949fd529507dfb803ab6b480cd73ca0ba00ca19c438849e2cea262a1c57d8f81cd257fb58e19dec7904da97d8386e87b84948169", + qx: "69b7667056e1e11d6caf6e45643f8b21e7a4bebda463c7fdbc13bc98efbd0214", + qy: "d3f9b12eb46c7c6fda0da3fc85bc1fd831557f9abc902a3be3cb3e8be7d1aa2f", + r: "288f7a1cd391842cce21f00e6f15471c04dc182fe4b14d92dc18910879799790", + s: "247b3c4e89a3bcadfea73c7bfd361def43715fa382b8c3edf4ae15d6e55e9979", + result: &SigVerResult::MsgChanged + }, + Fips186EcdsaTestVector { + msg: "3fe60dd9ad6caccf5a6f583b3ae65953563446c4510b70da115ffaa0ba04c076115c7043ab8733403cd69c7d14c212c655c07b43a7c71b9a4cffe22c2684788ec6870dc2013f269172c822256f9e7cc674791bf2d8486c0f5684283e1649576efc982ede17c7b74b214754d70402fb4bb45ad086cf2cf76b3d63f7fce39ac970", + qx: "bf02cbcf6d8cc26e91766d8af0b164fc5968535e84c158eb3bc4e2d79c3cc682", + qy: "069ba6cb06b49d60812066afa16ecf7b51352f2c03bd93ec220822b1f3dfba03", + r: "f5acb06c59c2b4927fb852faa07faf4b1852bbb5d06840935e849c4d293d1bad", + s: "049dab79c89cc02f1484c437f523e080a75f134917fda752f2d5ca397addfe5d", + result: &SigVerResult::SChanged + }, + Fips186EcdsaTestVector { + msg: "983a71b9994d95e876d84d28946a041f8f0a3f544cfcc055496580f1dfd4e312a2ad418fe69dbc61db230cc0c0ed97e360abab7d6ff4b81ee970a7e97466acfd9644f828ffec538abc383d0e92326d1c88c55e1f46a668a039beaa1be631a89129938c00a81a3ae46d4aecbf9707f764dbaccea3ef7665e4c4307fa0b0a3075c", + qx: "224a4d65b958f6d6afb2904863efd2a734b31798884801fcab5a590f4d6da9de", + qy: "178d51fddada62806f097aa615d33b8f2404e6b1479f5fd4859d595734d6d2b9", + r: "87b93ee2fecfda54deb8dff8e426f3c72c8864991f8ec2b3205bb3b416de93d2", + s: "4044a24df85be0cc76f21a4430b75b8e77b932a87f51e4eccbc45c263ebf8f66", + result: &SigVerResult::RChanged + }, + Fips186EcdsaTestVector { + msg: "4a8c071ac4fd0d52faa407b0fe5dab759f7394a5832127f2a3498f34aac287339e043b4ffa79528faf199dc917f7b066ad65505dab0e11e6948515052ce20cfdb892ffb8aa9bf3f1aa5be30a5bbe85823bddf70b39fd7ebd4a93a2f75472c1d4f606247a9821f1a8c45a6cb80545de2e0c6c0174e2392088c754e9c8443eb5af", + qx: "43691c7795a57ead8c5c68536fe934538d46f12889680a9cb6d055a066228369", + qy: "f8790110b3c3b281aa1eae037d4f1234aff587d903d93ba3af225c27ddc9ccac", + r: "8acd62e8c262fa50dd9840480969f4ef70f218ebf8ef9584f199031132c6b1ce", + s: "cfca7ed3d4347fb2a29e526b43c348ae1ce6c60d44f3191b6d8ea3a2d9c92154", + result: &SigVerResult::SChanged + }, + Fips186EcdsaTestVector { + msg: "0a3a12c3084c865daf1d302c78215d39bfe0b8bf28272b3c0b74beb4b7409db0718239de700785581514321c6440a4bbaea4c76fa47401e151e68cb6c29017f0bce4631290af5ea5e2bf3ed742ae110b04ade83a5dbd7358f29a85938e23d87ac8233072b79c94670ff0959f9c7f4517862ff829452096c78f5f2e9a7e4e9216", + qx: "9157dbfcf8cf385f5bb1568ad5c6e2a8652ba6dfc63bc1753edf5268cb7eb596", + qy: "972570f4313d47fc96f7c02d5594d77d46f91e949808825b3d31f029e8296405", + r: "dfaea6f297fa320b707866125c2a7d5d515b51a503bee817de9faa343cc48eeb", + s: "8f780ad713f9c3e5a4f7fa4c519833dfefc6a7432389b1e4af463961f09764f2", + result: &SigVerResult::MsgChanged + }, + Fips186EcdsaTestVector { + msg: "785d07a3c54f63dca11f5d1a5f496ee2c2f9288e55007e666c78b007d95cc28581dce51f490b30fa73dc9e2d45d075d7e3a95fb8a9e1465ad191904124160b7c60fa720ef4ef1c5d2998f40570ae2a870ef3e894c2bc617d8a1dc85c3c55774928c38789b4e661349d3f84d2441a3b856a76949b9f1f80bc161648a1cad5588e", + qx: "072b10c081a4c1713a294f248aef850e297991aca47fa96a7470abe3b8acfdda", + qy: "9581145cca04a0fb94cedce752c8f0370861916d2a94e7c647c5373ce6a4c8f5", + r: "09f5483eccec80f9d104815a1be9cc1a8e5b12b6eb482a65c6907b7480cf4f19", + s: "a4f90e560c5e4eb8696cb276e5165b6a9d486345dedfb094a76e8442d026378d", + result: &SigVerResult::QChanged + }, + Fips186EcdsaTestVector { + msg: "76f987ec5448dd72219bd30bf6b66b0775c80b394851a43ff1f537f140a6e7229ef8cd72ad58b1d2d20298539d6347dd5598812bc65323aceaf05228f738b5ad3e8d9fe4100fd767c2f098c77cb99c2992843ba3eed91d32444f3b6db6cd212dd4e5609548f4bb62812a920f6e2bf1581be1ebeebdd06ec4e971862cc42055ca", + qx: "09308ea5bfad6e5adf408634b3d5ce9240d35442f7fe116452aaec0d25be8c24", + qy: "f40c93e023ef494b1c3079b2d10ef67f3170740495ce2cc57f8ee4b0618b8ee5", + r: "5cc8aa7c35743ec0c23dde88dabd5e4fcd0192d2116f6926fef788cddb754e73", + s: "9c9c045ebaa1b828c32f82ace0d18daebf5e156eb7cbfdc1eff4399a8a900ae7", + result: &SigVerResult::MsgChanged + }, + Fips186EcdsaTestVector { + msg: "60cd64b2cd2be6c33859b94875120361a24085f3765cb8b2bf11e026fa9d8855dbe435acf7882e84f3c7857f96e2baab4d9afe4588e4a82e17a78827bfdb5ddbd1c211fbc2e6d884cddd7cb9d90d5bf4a7311b83f352508033812c776a0e00c003c7e0d628e50736c7512df0acfa9f2320bd102229f46495ae6d0857cc452a84", + qx: "2d98ea01f754d34bbc3003df5050200abf445ec728556d7ed7d5c54c55552b6d", + qy: "9b52672742d637a32add056dfd6d8792f2a33c2e69dafabea09b960bc61e230a", + r: "06108e525f845d0155bf60193222b3219c98e3d49424c2fb2a0987f825c17959", + s: "62b5cdd591e5b507e560167ba8f6f7cda74673eb315680cb89ccbc4eec477dce", + result: &SigVerResult::Ok + }, + ]; + + #[test] + fn wycheproof_test() -> Result<(), HostError> { + use wycheproof::ecdsa::{TestName::EcdsaSecp256r1Sha256, TestSet}; + use wycheproof::TestResult; + + let test_set = TestSet::load(EcdsaSecp256r1Sha256).unwrap(); + let host = Host::default(); + let mut vmcaller = VmCaller::none(); + for test_group in test_set.test_groups { + let public_key = host.bytes_new_from_slice(&test_group.key.key).unwrap(); + for test in test_group.tests { + // reset the budget for each test case so they don't interfere + host.as_budget().reset_default()?; + let msg = host.bytes_new_from_slice(test.msg.as_slice())?; + let msg_digest = host.compute_hash_sha256(&mut vmcaller, msg)?; + + let mut sig = match Signature::from_der(&test.sig) { + Ok(s) => s.normalize_s().unwrap_or(s), + Err(_) => { + // If the signature is not a valid DER-encoded one, we skip + // it. Since our host function expects signatures to be + // fixed-witdh bytes encoded, we don't have to validate it. + assert_ne!(test.result, TestResult::Valid); + continue; + } + }; + // Wycheproof tests do not enforce low s but we do, so we need to normalize + sig = sig.normalize_s().unwrap_or(sig); + let signature = host.bytes_new_from_slice(&sig.to_bytes())?; + + match host.verify_sig_ecdsa_secp256r1( + &mut vmcaller, + public_key, + msg_digest, + signature, + ) { + Ok(_) => assert_eq!(test.result, TestResult::Valid), + // we treat `TestResult::Acceptable` as invalid + Err(_) => { + assert_ne!(test.result, TestResult::Valid) + } + } + } + } + Ok(()) + } +}