Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add secp256r1 host function for signature verification #1376

Merged
merged 15 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
.vscode
/vendor
lcov.info
lcov.info
dmkozh marked this conversation as resolved.
Show resolved Hide resolved
.DS_Store
43 changes: 40 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
23 changes: 22 additions & 1 deletion soroban-env-common/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
},
Expand Down
12 changes: 10 additions & 2 deletions soroban-env-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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"]

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Secp256k1>;

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(),
}
}
}
14 changes: 14 additions & 0 deletions soroban-env-host/benches/common/cost_types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
#[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;
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::*;
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::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }
}
}
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
Original file line number Diff line number Diff line change
@@ -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(),
}
}
}
Loading
Loading