diff --git a/Cargo.lock b/Cargo.lock index d23defb584..871d778d6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,7 @@ dependencies = [ "hex", "hex-literal", "k256", + "p256", "rand_core 0.6.4", "serde", "serde_json", @@ -814,6 +815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -940,6 +942,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -1474,6 +1477,18 @@ version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +[[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 0.10.6", +] + [[package]] name = "parking_lot_core" version = "0.9.7" @@ -1487,6 +1502,15 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1574,6 +1598,15 @@ dependencies = [ "termtree", ] +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error" version = "1.0.4" diff --git a/packages/crypto/Cargo.toml b/packages/crypto/Cargo.toml index 8bbb5c4e42..428d89a0a7 100644 --- a/packages/crypto/Cargo.toml +++ b/packages/crypto/Cargo.toml @@ -24,6 +24,7 @@ ed25519-zebra = "3" digest = "0.10" rand_core = { version = "0.6", features = ["getrandom"] } thiserror = "1.0.38" +p256 = { version = "0.13.2", features = ["ecdsa"] } [dev-dependencies] criterion = "0.4" diff --git a/packages/crypto/README.md b/packages/crypto/README.md index cf927879a7..180fcb07ab 100644 --- a/packages/crypto/README.md +++ b/packages/crypto/README.md @@ -8,7 +8,9 @@ and [cosmwasm-std](`https://crates.io/crates/cosmwasm-std`) crates. ## Implementations -- `secp256k1_verify()`: Digital signature verification using the ECDSA sepc256k1 +- `secp256k1_verify()`: Digital signature verification using the ECDSA secp256k1 + scheme, for Cosmos signature / public key formats. +- `secp256r1_verify()`: Digital signature verification using the ECDSA secp256r1 scheme, for Cosmos signature / public key formats. - `ed25519_verify()`: Digital signature verification using the EdDSA ed25519 scheme, for Tendermint signature / public key formats. diff --git a/packages/crypto/benches/main.rs b/packages/crypto/benches/main.rs index ce4b8d2eb3..69a179589a 100644 --- a/packages/crypto/benches/main.rs +++ b/packages/crypto/benches/main.rs @@ -11,7 +11,7 @@ use k256::ecdsa::SigningKey; // type alias use sha2::Sha256; use cosmwasm_crypto::{ - ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify, + ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify, secp256r1_verify }; use std::cmp::min; diff --git a/packages/crypto/src/lib.rs b/packages/crypto/src/lib.rs index 01ee97bbf7..bce714441d 100644 --- a/packages/crypto/src/lib.rs +++ b/packages/crypto/src/lib.rs @@ -9,6 +9,7 @@ mod ed25519; mod errors; mod identity_digest; mod secp256k1; +mod secp256r1; #[doc(hidden)] pub use crate::ed25519::EDDSA_PUBKEY_LEN; @@ -20,3 +21,5 @@ pub use crate::errors::{CryptoError, CryptoResult}; pub use crate::secp256k1::{secp256k1_recover_pubkey, secp256k1_verify}; #[doc(hidden)] pub use crate::secp256k1::{ECDSA_PUBKEY_MAX_LEN, ECDSA_SIGNATURE_LEN, MESSAGE_HASH_MAX_LEN}; +#[doc(hidden)] +pub use crate::secp256r1::secp256r1_verify; diff --git a/packages/crypto/src/secp256r1.rs b/packages/crypto/src/secp256r1.rs new file mode 100644 index 0000000000..52aaf96c1e --- /dev/null +++ b/packages/crypto/src/secp256r1.rs @@ -0,0 +1,277 @@ +use digest::{Digest, Update}; // trait +use p256::{ + ecdsa::signature::DigestVerifier, // traits + ecdsa::{Signature, VerifyingKey}, // type aliases + elliptic_curve::sec1::ToEncodedPoint, +}; +use std::convert::TryInto; + +use crate::errors::{CryptoError, CryptoResult}; +use crate::identity_digest::Identity256; + +/// Max length of a message hash for secp256r1 verification in bytes. +/// This is typically a 32 byte output of e.g. SHA-256 or Keccak256. In theory shorter values +/// are possible but currently not supported by the implementation. Let us know when you need them. +pub const MESSAGE_HASH_MAX_LEN: usize = 32; + +/// ECDSA (secp256r1) parameters +/// Length of a serialized signature +pub const ECDSA_SIGNATURE_LEN: usize = 64; + +/// Length of a serialized compressed public key +const ECDSA_COMPRESSED_PUBKEY_LEN: usize = 33; +/// Length of a serialized uncompressed public key +const ECDSA_UNCOMPRESSED_PUBKEY_LEN: usize = 65; +/// Max length of a serialized public key +pub const ECDSA_PUBKEY_MAX_LEN: usize = ECDSA_UNCOMPRESSED_PUBKEY_LEN; + +/// ECDSA secp256r1 implementation. +/// +/// This function verifies message hashes (typically, hashed using SHA-256) against a signature, +/// with the public key of the signer, using the secp256r1 elliptic curve digital signature +/// parametrization / algorithm. +/// +/// The signature and public key are in "Cosmos" format: +/// - signature: Serialized "compact" signature (64 bytes). +/// - public key: [Serialized according to SEC 2](https://www.oreilly.com/library/view/programming-bitcoin/9781492031482/ch04.html) +/// (33 or 65 bytes). +pub fn secp256r1_verify( + message_hash: &[u8], + signature: &[u8], + public_key: &[u8], +) -> CryptoResult { + let message_hash = read_hash(message_hash)?; + let signature = read_signature(signature)?; + check_pubkey(public_key)?; + + // Already hashed, just build Digest container + let message_digest = Identity256::new().chain(message_hash); + + let mut signature = Signature::from_bytes(&signature.into()) + .map_err(|e| CryptoError::generic_err(e.to_string()))?; + + // High-S signatures require normalization since our verification implementation + // rejects them by default. If we had a verifier that does not restrict to + // low-S only, this step was not needed. + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + } + + let public_key = VerifyingKey::from_sec1_bytes(public_key) + .map_err(|e| CryptoError::generic_err(e.to_string()))?; + + match public_key.verify_digest(message_digest, &signature) { + Ok(()) => Ok(true), + Err(_) => Ok(false), + } +} + +/// Error raised when hash is not 32 bytes long +struct InvalidSecp256r1HashFormat; + +impl From for CryptoError { + fn from(_original: InvalidSecp256r1HashFormat) -> Self { + CryptoError::invalid_hash_format() + } +} + +fn read_hash(data: &[u8]) -> Result<[u8; 32], InvalidSecp256r1HashFormat> { + data.try_into().map_err(|_| InvalidSecp256r1HashFormat) +} + +/// Error raised when signature is not 64 bytes long (32 bytes r, 32 bytes s) +struct InvalidSecp256r1SignatureFormat; + +impl From for CryptoError { + fn from(_original: InvalidSecp256r1SignatureFormat) -> Self { + CryptoError::invalid_signature_format() + } +} + +fn read_signature(data: &[u8]) -> Result<[u8; 64], InvalidSecp256r1SignatureFormat> { + data.try_into().map_err(|_| InvalidSecp256r1SignatureFormat) +} + +/// Error raised when public key is not in one of the two supported formats: +/// 1. Uncompressed: 65 bytes starting with 0x04 +/// 2. Compressed: 33 bytes starting with 0x02 or 0x03 +struct InvalidSecp256r1PubkeyFormat; + +impl From for CryptoError { + fn from(_original: InvalidSecp256r1PubkeyFormat) -> Self { + CryptoError::invalid_pubkey_format() + } +} + +fn check_pubkey(data: &[u8]) -> Result<(), InvalidSecp256r1PubkeyFormat> { + let ok = match data.first() { + Some(0x02) | Some(0x03) => data.len() == ECDSA_COMPRESSED_PUBKEY_LEN, + Some(0x04) => data.len() == ECDSA_UNCOMPRESSED_PUBKEY_LEN, + _ => false, + }; + if ok { + Ok(()) + } else { + Err(InvalidSecp256r1PubkeyFormat) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // use elliptic_curve::rand_core::OsRng; + // use elliptic_curve::sec1::ToEncodedPoint; + + use hex_literal::hex; + use p256::{ + ecdsa::signature::DigestSigner, // trait + ecdsa::SigningKey, // type alias + elliptic_curve::rand_core::OsRng, + }; + use serde::Deserialize; + use sha2::Sha256; + use std::fs::File; + use std::io::BufReader; + + // For generic signature verification + const MSG: &str = "Hello World!"; + + // Cosmos secp256r1 signature verification + // tendermint/PubKeySecp256r1 pubkey + const COSMOS_SECP256R1_PUBKEY_HEX: &str = + "049a2c7b27b132246e170dfb9167db5c5bd302033dbece2bc3f2541a6cd11851821a775f1fc6c4f89e0d019888057f0d574f1c4eb1f90a7a41c4ea9b99b538d932"; + + const COSMOS_SECP256R1_MSG_HEX1: &str = "6265206b696e64"; + const COSMOS_SECP256R1_MSG_HEX2: &str = "6265206b696e64"; + const COSMOS_SECP256R1_MSG_HEX3: &str = "6265206b696e64"; + + const COSMOS_SECP256R1_SIGNATURE_HEX1: &str = "453020029250fb9eb22b21b881319a123244e463a329356b75ce804fc2dda174e715104621028d009abee7d523894b425d974bc38cfae5d05cdf5a550c8eceae1f20f0c9913f0038"; + const COSMOS_SECP256R1_SIGNATURE_HEX2: &str = "30450220658fc9271b09bd53edf3a5bd31b7bd99bd3c3de7859cd8dd1133e76ed44fcb580221009e43d091911de0fc90d22960517211f5cf6c624b326759e219326f3af807ac31"; + const COSMOS_SECP256R1_SIGNATURE_HEX3: &str = "30450220658fc9271b09bd53edf3a5bd31b7bd99bd3c3de7859cd8dd1133e76ed44fcb580221009e43d091911de0fc90d22960517211f5cf6c624b326759e219326f3af807ac31"; + + // Test data originally from https://github.com/cosmos/cosmjs/blob/v0.24.0-alpha.22/packages/crypto/src/secp256k1.spec.ts#L195-L394 + const COSMOS_SECP256R1_TESTS_JSON: &str = "./testdata/secp256r1_tests.json"; + + #[test] + fn test_secp256r1_verify() { + // Explicit / external hashing + let message_digest = Sha256::new().chain(MSG); + let message_hash = message_digest.clone().finalize(); + + // Signing + let secret_key = SigningKey::random(&mut OsRng); // Serialize with `::to_bytes()` + + // Note: the signature type must be annotated or otherwise inferrable as + // `Signer` has many impls of the `Signer` trait (for both regular and + // recoverable signature types). + let signature: Signature = secret_key.sign_digest(message_digest); + + let public_key = VerifyingKey::from(&secret_key); // Serialize with `::to_encoded_point()` + + // Verification (uncompressed public key) + assert!(secp256r1_verify( + &message_hash, + signature.to_bytes().as_slice(), + public_key.to_encoded_point(false).as_bytes() + ) + .unwrap()); + + // Verification (compressed public key) + assert!(secp256r1_verify( + &message_hash, + signature.to_bytes().as_slice(), + public_key.to_encoded_point(true).as_bytes() + ) + .unwrap()); + + // Wrong message fails + let bad_message_hash = Sha256::new().chain(MSG).chain("\0").finalize(); + assert!(!secp256r1_verify( + &bad_message_hash, + signature.to_bytes().as_slice(), + public_key.to_encoded_point(false).as_bytes() + ) + .unwrap()); + + // Other pubkey fails + let other_secret_key = SigningKey::random(&mut OsRng); + let other_public_key = VerifyingKey::from(&other_secret_key); + assert!(!secp256r1_verify( + &message_hash, + signature.to_bytes().as_slice(), + other_public_key.to_encoded_point(false).as_bytes() + ) + .unwrap()); + } + + // #[test] + // fn test_cosmos_secp256r1_verify() { + // let public_key = hex::decode(COSMOS_SECP256R1_PUBKEY_HEX).unwrap(); + + // for ((i, msg), sig) in (1..) + // .zip(&[ + // COSMOS_SECP256R1_MSG_HEX1, + // //COSMOS_SECP256R1_MSG_HEX2, + // //COSMOS_SECP256R1_MSG_HEX3, + // ]) + // .zip(&[ + // COSMOS_SECP256R1_SIGNATURE_HEX1, + // //COSMOS_SECP256R1_SIGNATURE_HEX2, + // //COSMOS_SECP256R1_SIGNATURE_HEX3, + // ]) + // { + // let message = hex::decode(msg).unwrap(); + // let signature = hex::decode(sig).unwrap(); + + // // Explicit hash + // let message_hash = Sha256::digest(&message); + + // // secp256r1_verify works + // let valid = secp256r1_verify(&message_hash, &signature, &public_key).unwrap(); + // assert!(valid, "secp256r1_verify() failed (test case {i})",); + // } + // } + + // #[test] + // fn test_cosmos_extra_secp256r1_verify() { + // use std::fs::File; + // use std::io::BufReader; + + // use serde::Deserialize; + + // #[derive(Deserialize, Debug)] + // struct Encoded { + // message: String, + // message_hash: String, + // signature: String, + // #[serde(rename = "pubkey")] + // public_key: String, + // } + + // // Open the file in read-only mode with buffer. + // let file = File::open(COSMOS_SECP256R1_TESTS_JSON).unwrap(); + // let reader = BufReader::new(file); + + // let codes: Vec = serde_json::from_reader(reader).unwrap(); + + // for (i, encoded) in (1..).zip(codes) { + // let message = hex::decode(&encoded.message).unwrap(); + + // let hash = hex::decode(&encoded.message_hash).unwrap(); + // let message_hash = Sha256::digest(&message); + // assert_eq!(hash.as_slice(), message_hash.as_slice()); + + // let signature = hex::decode(&encoded.signature).unwrap(); + + // let public_key = hex::decode(&encoded.public_key).unwrap(); + + // // secp256r1_verify() works + // let valid = secp256r1_verify(&message_hash, &signature, &public_key).unwrap(); + // assert!( + // valid, + // "secp256r1_verify failed (test case {i} in {COSMOS_SECP256R1_TESTS_JSON})" + // ); + // } + // } +} diff --git a/packages/crypto/testdata/secp256k1_tests.json b/packages/crypto/testdata/secp256k1_tests.json index 223c6938e1..edb4c15b9a 100644 --- a/packages/crypto/testdata/secp256k1_tests.json +++ b/packages/crypto/testdata/secp256k1_tests.json @@ -119,4 +119,4 @@ "signature": "5c707b6df7667324f950216b933d28e307a0223b24d161bc5887208d7f880b3a4b7bc56586dc51d806ac3ad72807bc62d1d06d0812f121bd91e9770d84885c39", "pubkey": "0436748f1a531e91a40b9e6bfc502488cfd749c3b9529633d4e4dabdb058708b7ac4b0672c9a7105b6dbf63b8054e5d266d43d37cf51241ce289d8f9140fe28996" } -] \ No newline at end of file +] diff --git a/packages/crypto/testdata/secp256r1_tests.json b/packages/crypto/testdata/secp256r1_tests.json new file mode 100644 index 0000000000..edb4c15b9a --- /dev/null +++ b/packages/crypto/testdata/secp256r1_tests.json @@ -0,0 +1,122 @@ +[ + { + "message": "5c868fedb8026979ebd26f1ba07c27eedf4ff6d10443505a96ecaf21ba8c4f0937b3cd23ffdc3dd429d4cd1905fb8dbcceeff1350020e18b58d2ba70887baa3a9b783ad30d3fbf210331cdd7df8d77defa398cdacdfc2e359c7ba4cae46bb74401deb417f8b912a1aa966aeeba9c39c7dd22479ae2b30719dca2f2206c5eb4b7", + "message_hash": "5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0", + "signature": "207082eb2c3dfa0b454e0906051270ba4074ac93760ba9e7110cd9471475111151eb0dbbc9920e72146fb564f99d039802bf6ef2561446eb126ef364d21ee9c4", + "pubkey": "04051c1ee2190ecfb174bfe4f90763f2b4ff7517b70a2aec1876ebcfd644c4633fb03f3cfbd94b1f376e34592d9d41ccaf640bb751b00a1fadeb0c01157769eb73" + }, + { + "message": "17cd4a74d724d55355b6fb2b0759ca095298e3fd1856b87ca1cb2df5409058022736d21be071d820b16dfc441be97fbcea5df787edc886e759475469e2128b22f26b82ca993be6695ab190e673285d561d3b6d42fcc1edd6d12db12dcda0823e9d6079e7bc5ff54cd452dad308d52a15ce9c7edd6ef3dad6a27becd8e001e80f", + "message_hash": "586052916fb6f746e1d417766cceffbe1baf95579bab67ad49addaaa6e798862", + "signature": "626d61b7be1488b563e8a85bfb623b2331903964b5c0476c9f9ad29144f076fe2002a2c0ab5e48626bf761cf677dfeede9c7309d2436d4b8c2b89f21ee2ebc6a", + "pubkey": "04ff28290d214a783da7d76098b632c387b3dd5fd33677df8ca7109e3c941e5df2e5a6530c0bb58fbba14ecbc3d76a2a3d5017c66fc260f9214a028b8b3a7b2dce" + }, + { + "message": "db0d31717b04802adbbae1997487da8773440923c09b869e12a57c36dda34af11b8897f266cd81c02a762c6b74ea6aaf45aaa3c52867eb8f270f5092a36b498f88b65b2ebda24afe675da6f25379d1e194d093e7a2f66e450568dbdffebff97c4597a00c96a5be9ba26deefcca8761c1354429622c8db269d6a0ec0cc7a8585c", + "message_hash": "c36d0ecf4bfd178835c97aae7585f6a87de7dfa23cc927944f99a8d60feff68b", + "signature": "83de9be443bcf480892b8c8ca1d5ee65c79a315642c3f7b5305aff3065fda2789747932122b93cec42cad8ee4630a8f6cbe127578b8c495b4ab927275f657658", + "pubkey": "048f83776cbb355dbe59756c6b3d9b40a3470b6c64d49d9361a73bafcc1e7ba69beb05acb126fc4358b3c7008e1e864b4327086157412a84700d82672de27cb627" + }, + { + "message": "47c9deddfd8c841a63a99be96e972e40fa035ae10d929babfc86c437b9d5d495577a45b7f8a35ce3f880e7d8ae8cd8eb685cf41f0506e68046ccf5559232c674abb9c3683829dcc8002683c4f4ca3a29a7bfde20d96dd0f1a0ead847dea18f297f220f94932536ca4deacedc2c6701c3ee50e28e358dcc54cdbf69daf0eb87f6", + "message_hash": "a761293b02c5d8327f909d61a38173556c1f1f770c488810a9b360cf7786c148", + "signature": "723da69da81c8f6b081a9a728b9bba785d2067e0ed769675f8a7563d22ed8a163a993793cf39b96b3cd625df0e06f206e17579cd8ebcb7e704174c3d94dba684", + "pubkey": "0429b0c44bd3887a9ab60fd1c9bb81c9ab421e51b4736cedfa75e4cf960e6098315eab750d173d20842dfdebf861ea7898fdea0527211f8c86f31d9d48b6b65e63" + }, + { + "message": "f15433188c2bbc93b2150bb2f34d36ba8ae49f8f7e4e81aed651c8fb2022b2a7e851c4dbbbc21c14e27380316bfdebb0a049246349537dba687581c1344e40f75afd2735bb21ea074861de6801d28b22e8beb76fdd25598812b2061ca3fba229daf59a4ab416704543b02e16b8136c22acc7e197748ae19b5cbbc160fdc3a8cd", + "message_hash": "08ec76ab0f1bc9dc27b3b3bd4f949c60ecc8bbf27678b28f2ee8de055ee8bf59", + "signature": "0e0c5228e6783bee4d0406f4f7b7d79f705f0dbb55126966f79e631bd8b23079faae33aec5b0fafd3413c14bfdef9c7c9ac6abd06c923c48ab136a2c56826118", + "pubkey": "04441ef6e211e3fa6cffce9d34b4765487bc0d98d95b21ccfaeef42d7de2fef9a57886a20b6b10f97f5d05caa904ca3e287b92955658566639031e67cd243d8657" + }, + { + "message": "1bc796124b87793b7f7fdd53b896f8f0d0f2d2be36d1944e3c2a0ac5c6b2839f59a4b4fad200f8035ec98630c51ef0d40863a5ddd69b703d73f06b4afae8ad1a88e19b1b26e8c10b7bff953c05eccc82fd771b220910165f3a906b7c931683e431998d1fd06c32dd11b4f872bf980d547942f22124c7e50c9523321aee23a36d", + "message_hash": "ffbe3fd342a1a991848d02258cf5e3df301974b7a8f0fe10a88222a9503f67e0", + "signature": "b9d3962edadc893f8eeff379f136c7b8fc6ea824a5afc6cbda7e3cb4c7a1e860bb1c1f901cf450edfdce20686352bb0cf0a643301123140ec87c92480d7f9d6a", + "pubkey": "04ad98fbf6748401963e4c732b0ba4c4b5016f1d87935c5edf84b55c38b28eb3cf35f91b470e007cc5f5c2e12c9b8725bc92a6feb259968436881c72906a14ca84" + }, + { + "message": "18e55ac264031da435b613fc9dc6c4aafc49aae8ddf6f220d523415896ff915fae5c5b2e6aed61d88e5721823f089c46173afc5d9b47fd917834c85284f62dda6ed2d7a6ff10eb553b9312b05dad7decf7f73b69479c02f14ea0a2aa9e05ec07396cd37c28795c90e590631137102315635d702278e352aa41d0826adadff5e1", + "message_hash": "434fea583df79f781e41f18735a24409cf404f28e930290cc97c67ef158e5789", + "signature": "9369ab86afae5e22ed5f4012964804d2a19c36b8b58cf2855205b1cfcc937422a27dfc38d899b78edcf38a1b2b53578e72270b083d7d69424c4b4a7d25d39f4d", + "pubkey": "04061d7152dc6263a6764c6e810ee02f6333c844d90d70043b2bb6d4efacfac78e4391f7f17063bd69bd0d19061cc09eeb429561f3d88a3b38019ac82ca152c35c" + }, + { + "message": "a5290666c97294d090f8da898e555cbd33990579e5e95498444bfb318b4aa1643e0d4348425e21c7c6f99f9955f3048f56c22b68c4a516af5c90ed5268acc9c5a20fec0200c2a282a90e20d3c46d4ecdda18ba18b803b19263de2b79238da921707a0864799cdee9f02913b40681c02c6923070688844b58fe415b7d71ea6845", + "message_hash": "c352f58e118fc0d7810b8020bdb306b7dc115b41bbb0b642c7ea73a60cc2a4eb", + "signature": "c5e439cef76b28dc0fe9d260763bec05b5e795ac8d90b25d9fccbc1918bc32f31b06144e6b191224d5eda822a5b3b2026af6aa7f25a9061c9e81c312728aa94a", + "pubkey": "043eab98ab69bca21ad18136a092a4f398cb0a7d34c4bf18bbe9c81bdfce3dde752f8a82a4c1cbe5535d4acd9218ba6df4a96db88963d36108d562795473dedeae" + }, + { + "message": "13ad0600229c2a66b2f11617f69c7210ad044c49265dc98ec3c64f56e56a083234d277d404e2c40523c414ad23af5cc2f91a47fe59e7ca572f7fe1d3d3cfceaedadac4396749a292a38e92727273272335f12b2acea21cf069682e67d7e7d7a31ab5bb8e472298a9451aeae6f160f36e6623c9b632b9c93371a002818addc243", + "message_hash": "6ff9153ede285fc0e486f1dd4dd9e32a0fb23e9653c55841b67c2e5a090aac63", + "signature": "ee8615a5fab6fc674e6d3d9cde8da2b18dece076ae94d96662e16109db12d7203171705cdab2b3d34c58e556c80358c105807e98243f5754b70b771071308b94", + "pubkey": "042aaf49401b01083bf0657a379530f1b2b5db414e3fe91fca07048c89df05a4dae584ef2fe20d2f3293ff46df73155a76218349336977250055dc9ae7f28e57e0" + }, + { + "message": "51ad843da5eafc177d49a50a82609555e52773c5dfa14d7c02db5879c11a6b6e2e0860df38452dc579d763f91a83ade23b73f4fcbd703f35dd6ecfbb4c9578d5b604ed809c8633e6ac5679a5f742ce94fea3b97b5ba8a29ea28101a7b35f9eaa894dda54e3431f2464d18faf8342b7c59dfe0598c0ab29a14622a08eea70126b", + "message_hash": "8e19143e34fee546fab3d56e816f2e21586e27912a2ad7d80af75942e0ff585a", + "signature": "f753c447161aa3a58e5deeca31797f21484fb0ec3a7fe6e464ab1914896f253b99640fbcce1f25fd66744b046e0dfd57fa23070555f438af6c5e5828d47e9fa7", + "pubkey": "048c59a63c27b0b4e1e1b67f6e97180d5fa679791cde6af650271a274c369361421b42e36a7029f8982d346264f81f46cbc2e8c823568b2ad15649794fc835f87e" + }, + { + "message": "678b505467d55ce01aec23fd4851957137c3a1de3ff2c673ec95a577aa9fb011b4b4a8eb7a0e6f391d4236a35b7e769692ace5851d7c53700e180fa522d3d37dbaa496163f3de6d96391e38ff83271e621f2458729ff74de462cdce6b3029f308d4eb8aef036357b9de06d68558e0388a6e88af91340c875050b8c91c4e26fc8", + "message_hash": "6fe86a3b533114e1db444217999ce5907237e69acc47cfb8d30b4e14ee58817a", + "signature": "439fd0423bde36a1616a6fa4343bb7e07a6b3f6dc629aa8c93c91831055e476c20998a26ae4b96ef36d48d83e8af0288f0bbc2db5ca5c8271a42f3fdc478fcb2", + "pubkey": "04817e21214a40f8135e2053656dc3d9a66eade80f9428f347d11ea249095e9513df446f64d7db0e8161fc1c03c60ddae5d73a8d80ad134600acefc06b3c8c9eda" + }, + { + "message": "9bf457159f0d44b78d0e151ee53c41cecd98fb4e4129fcda8cc84a758636f84dcad9032f3ec422219d8a7ec61ea89f45d19cab3c3d451de1a634e3d2532231bc03031973d7150cf8e83d8b6a34f25fc136446878e3851b780abdca069c8e981b3ea3f1bf1ff6e47a03f97aed64c1cc90dd00389fa21bb973f142af5e8ceccef4", + "message_hash": "03b3e33ade25fad2eeb530433b6785fb1e977228e0049e572437caa33baa059e", + "signature": "4ce72a83cf1d148db4d1e46e2f773c677f72933c40d7100b9192750a1c8222a89d5fbd67ce89ba8c79df9dc3b42922026a8498921c2bdb4ea8f36496d88c2cfb", + "pubkey": "04571a598076a318dcd7af6ff35c2ab35198197faa1df9612f951d516456c97ef7a30a1405958f554fdf1bad22d22333acb4fe4c968c74a281c524020ad949c9ef" + }, + { + "message": "2469172b7a046e6112dfe365590dfddb7c045cccd4ab353edc3076091aad1c780a9a73ff93f3dbf9e2189c5d1fdd6f6167d0ae8cc0f53dc8950e60dd0410e23589999d4ce4fa49e268774defd4edce01c05b205014b63591a041745bfffc6ae4d72d3add353e49478106653cc735b07b0fe665c42d0e6766e525bb9718264c87", + "message_hash": "b90651535080381884a0917c02fdf9e176798c4d65bed76568ec1de95e3c9641", + "signature": "1f1e1fb673e9a7dee09961c6824b473189904deb4f0d8e28da51f77f4de2efe6ae8df1fcdb226fac8b46e494720e45f6d9a5350174faaf22e47b6329ee6c5e1b", + "pubkey": "048db80c468a88ffb4bcdb5ccddbfc9da8cf728a651ab937edf729ad279d9375b50b97422368aa4b3ac13aa18c89ca2af86c14b1948325f1477d5ea9f5156bcc52" + }, + { + "message": "6f8983e74f304c3657cffde0682b42699cb2c3475b925058ff37292c40a0aa296690ad129730339ac60cf784225b2fd3db58297c8ce5889df7a48d3e74a363ae4135e8a234cab53ca4c11c031d561a6cf7dd47b925ed5bc4c2794ba7b74a868b0c3da31ff1e4540d0768612192a236d86f74fb8c73f375b71c62f1648c0e6126", + "message_hash": "1095c90de2734d4b9dfedefbdd3f76f592f0a3e1697d1321f0eab3f78129c8ce", + "signature": "9cf7d941dcbbbe61c2a6f5112cb518094e79e5d203891de2247e75fd532c3f21fc5a04579b2526f2543efd2a57e82b647da08b6924bff39cf021398a56ad70de", + "pubkey": "0406e22a39ec06c32bcb4d235f23630a198a884a43e8c200a7c3b3f68ef4002de902c574035c08d39857caa492088d35cff9b1d4e1b73e8414b7d71fe53167f7cf" + }, + { + "message": "6fbe6f0f178fdc8a3ad1a8eecb02d37108c5831281fe85e3ff8eeb66ca1082a217b6d602439948f828e140140412544f994da75b6efc203b295235deca060ecfc7b71f05e5af2acc564596772ddbfb4078b4665f6b85f4e70641af26e31f6a14e5c88604459df4eeeed9b77b33c4b82a3c1458bd2fd1dc7214c04f9c79c8f09b", + "message_hash": "164025d15dfec124cd37db98daf196cfbe44716968112dc8a4be24e350abd559", + "signature": "59cd6c2a30227afbd693d87b201d0989435d6e116c144276a5223466a822c0f2b01495efda969b3fd3a2c05aa098a4e04b0d0e748726fc6174627da15b143799", + "pubkey": "04db6816cec58836ef290e39429f506f00c5541376128bedea089b88094f42cd950b23e68cd57203d9fedeeacef680c26baf25be682ad808f308d2ddd5828fdeae" + }, + { + "message": "2b49de971bb0f705a3fb5914eb7638d72884a6c3550667dbfdf301adf26bde02f387fd426a31be6c9ff8bfe8690c8113c88576427f1466508458349fc86036afcfb66448b947707e791e71f558b2bf4e7e7507773aaf4e9af51eda95cbce0a0f752b216f8a54a045d47801ff410ee411a1b66a516f278327df2462fb5619470e", + "message_hash": "00c6fc53c1986d19a8a8b580ee553dc1240745d760647d1c0adf442c133c7f56", + "signature": "9eaf69170aeba44966afe957295526ee9852b5034c18dc5aeef3255c8567838aebd4c8de2c22b5cb8803d6e070186786f6d5dae2202b9f899276fa31a66cb3bb", + "pubkey": "04d2a23e34dcb8e1fbaaf2c3c0e83500824b3a122e83737f1251fa34ccf0d2fb8719030b3d567ed019ba62ee392990a9a23f88ee990f26ed50783e4f671572371f" + }, + { + "message": "1fa7201d96ad4d190415f2656d1387fa886afc38e5cd18b8c60da367acf32c627d2c9ea19ef3f030e559fc2a21695cdbb65ddf6ba36a70af0d3fa292a32de31da6acc6108ab2be8bd37843338f0c37c2d62648d3d49013edeb9e179dadf78bf885f95e712fcdfcc8a172e47c09ab159f3a00ed7b930f628c3c48257e92fc7407", + "message_hash": "fb5dd3b8d280fe7c4838f01b2a5c28493ed3084f46b40642600ba39e43fbff7b", + "signature": "91058d1b912514940e1002855cc930c01a21234bad88f607f213af495c32b69f005d387ce3de25f1b9bad1fb180de110686d91b461ae2972fa4e4a7018519870", + "pubkey": "048a86bbf4f7014a8ef557ff6a87681b5728957fc68342a1b2b0164eef5a3c7bb1cbe75d53a1a222990470161143bf18b5cec7c75616bbacdddf2f0af45514f9ca" + }, + { + "message": "74715fe10748a5b98b138f390f7ca9629c584c5d6ad268fc455c8de2e800b73fa1ea9aaee85de58baa2ce9ce68d822fc31842c6b153baef3a12bf6b4541f74af65430ae931a64c8b4950ad1c76b31aea8c229b3623390e233c112586aa5907bbe419841f54f0a7d6d19c003b91dc84bbb59b14ec477a1e9d194c137e21c75bbb", + "message_hash": "f4083aebe08c9bdb8c08ff844ffc207f80fa4406fb73bdbc1c6020f71281bdae", + "signature": "fe43eb9c38b506d118e20f8605ac8954fc0406efd306ba7ea5b07577a2735d15d589e91bf5014c7c360342ad135259dd7ae684e2c21234d7a912b43d148fcf19", + "pubkey": "04ab11970c6a01f45f6730b57d8eee6c9e1e7be824b34b27f9b45d2c5f5b6e601d4d946af7e4f5b98647634cfbacb4eecf7507c2fea1b114117902e4bafe9e1eb0" + }, + { + "message": "d10131982dd1a1d839aba383cd72855bf41061c0cb04dfa1acad3181f240341d744ca6002b52f25fb3c63f16d050c4a4ef2c0ebf5f16ce987558f4b9d4a5ad3c6b81b617de00e04ba32282d8bf223bfedbb325b741dfdc8f56fa85c65d42f05f6a1330d8cc6664ad32050dd7b9e3993f4d6c91e5e12cbd9e82196e009ad22560", + "message_hash": "cb017b280093879c4b114b52ea670f14e97b661074abccc8539a23280fe136b4", + "signature": "ccdbbd2500043bf7f705536d5984ab5f05fdc0fa3cf464d8c88f861e3fc8e54cd5c6342c08dcd8242e1daf3595cae968e320a025aa45ec4bc725795da3d1becb", + "pubkey": "040827dce898102fa27e52172e8246368b062ade896d34b7f25d8c24fca4a424f91d283f6ebac2b4f6167477ee624e2249c52a7c321e67b6fef711fba2f90d20d7" + }, + { + "message": "ef9dbd90ded96ad627a0a987ab90537a3e7acc1fdfa991088e9d999fd726e3ce1e1bd89a7df08d8c2bf51085254c89dc67bc21e8a1a93f33a38c18c0ce3880e958ac3e3dbe8aec49f981821c4ac6812dd29fab3a9ebe7fbd799fb50f12021b48d1d9abca8842547b3b99befa612cc8b4ca5f9412e0352e72ab1344a0ac2913db", + "message_hash": "5f1d77f456d7ed30acad33795b50733d54226e57df4281a43d3821d0762f12fe", + "signature": "5c707b6df7667324f950216b933d28e307a0223b24d161bc5887208d7f880b3a4b7bc56586dc51d806ac3ad72807bc62d1d06d0812f121bd91e9770d84885c39", + "pubkey": "0436748f1a531e91a40b9e6bfc502488cfd749c3b9529633d4e4dabdb058708b7ac4b0672c9a7105b6dbf63b8054e5d266d43d37cf51241ce289d8f9140fe28996" + } +] diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 7df3f22c95..7ca76546dd 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -58,6 +58,12 @@ extern "C" { recovery_param: u32, ) -> u64; + /// Verifies message hashes against a signature with a public key, using the + /// secp256r1 ECDSA parametrization. + /// Returns 0 on verification success, 1 on verification failure, and values + /// greater than 1 in case of error. + fn secp256r1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32; + /// Verifies a message against a signature with a public key, using the /// ed25519 EdDSA scheme. /// Returns 0 on verification success, 1 on verification failure, and values @@ -407,6 +413,32 @@ impl Api for ExternalApi { } } + fn secp256r1_verify( + &self, + message_hash: &[u8], + signature: &[u8], + public_key: &[u8], + ) -> Result { + let hash_send = build_region(message_hash); + let hash_send_ptr = &*hash_send as *const Region as u32; + let sig_send = build_region(signature); + let sig_send_ptr = &*sig_send as *const Region as u32; + let pubkey_send = build_region(public_key); + let pubkey_send_ptr = &*pubkey_send as *const Region as u32; + + let result = unsafe { secp256r1_verify(hash_send_ptr, sig_send_ptr, pubkey_send_ptr) }; + match result { + 0 => Ok(true), + 1 => Ok(false), + 2 => panic!("MessageTooLong must not happen. This is a bug in the VM."), + 3 => Err(VerificationError::InvalidHashFormat), + 4 => Err(VerificationError::InvalidSignatureFormat), + 5 => Err(VerificationError::InvalidPubkeyFormat), + 10 => Err(VerificationError::GenericErr), + error_code => Err(VerificationError::unknown_err(error_code)), + } + } + fn ed25519_verify( &self, message: &[u8], diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index 305af4afb1..f5c6e48fe2 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -215,6 +215,19 @@ impl Api for MockApi { Ok(pubkey.to_vec()) } + fn secp256r1_verify( + &self, + message_hash: &[u8], + signature: &[u8], + public_key: &[u8], + ) -> Result { + Ok(cosmwasm_crypto::secp256r1_verify( + message_hash, + signature, + public_key, + )?) + } + fn ed25519_verify( &self, message: &[u8], diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index a5402660d1..7884dd7305 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -164,6 +164,13 @@ pub trait Api { recovery_param: u8, ) -> Result, RecoverPubkeyError>; + fn secp256r1_verify( + &self, + message_hash: &[u8], + signature: &[u8], + public_key: &[u8], + ) -> Result; + fn ed25519_verify( &self, message: &[u8], diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index dfbbb606a8..72f60b3417 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -22,6 +22,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.addr_humanize", "env.secp256k1_verify", "env.secp256k1_recover_pubkey", + "env.secp256r1_verify", "env.ed25519_verify", "env.ed25519_batch_verify", "env.debug", @@ -638,6 +639,7 @@ mod tests { (import "env" "addr_humanize" (func (param i32 i32) (result i32))) (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32))) (import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64))) + (import "env" "secp256r1_verify" (func (param i32 i32 i32) (result i32))) (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32))) (import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32))) )"#, @@ -657,6 +659,7 @@ mod tests { (import "env" "addr_humanize" (func (param i32 i32) (result i32))) (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32))) (import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64))) + (import "env" "secp256r1_verify" (func (param i32 i32 i32) (result i32))) (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32))) (import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32))) (import "env" "spam01" (func (param i32 i32) (result i32))) diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 746126f1fe..29ce798279 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -34,6 +34,10 @@ pub struct GasConfig { pub secp256k1_verify_cost: u64, /// secp256k1 public key recovery cost pub secp256k1_recover_pubkey_cost: u64, + /// secp256r1 signature verification cost + pub secp256r1_verify_cost: u64, + /// secp256r1 public key recovery cost + pub secp256r1_recover_pubkey_cost: u64, /// ed25519 signature verification cost pub ed25519_verify_cost: u64, /// ed25519 batch signature verification cost @@ -51,6 +55,10 @@ impl Default for GasConfig { secp256k1_verify_cost: 154 * GAS_PER_US, // ~162 us in crypto benchmarks secp256k1_recover_pubkey_cost: 162 * GAS_PER_US, + // ~154 us in crypto benchmarks + secp256r1_verify_cost: 154 * GAS_PER_US, + // ~162 us in crypto benchmarks + secp256r1_recover_pubkey_cost: 162 * GAS_PER_US, // ~63 us in crypto benchmarks ed25519_verify_cost: 63 * GAS_PER_US, // Gas cost factors, relative to ed25519_verify cost @@ -501,6 +509,7 @@ mod tests { "addr_humanize" => Function::new_typed(&mut store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "secp256k1_recover_pubkey" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }), + "secp256r1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "ed25519_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "ed25519_batch_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "debug" => Function::new_typed(&mut store, |_a: u32| {}), diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 4924c888d8..1bfe887f2a 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -4,7 +4,8 @@ use std::cmp::max; use std::marker::PhantomData; use cosmwasm_crypto::{ - ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify, CryptoError, + ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify, + secp256r1_verify, CryptoError, }; use cosmwasm_crypto::{ ECDSA_PUBKEY_MAX_LEN, ECDSA_SIGNATURE_LEN, EDDSA_PUBKEY_LEN, MESSAGE_HASH_MAX_LEN, @@ -323,6 +324,48 @@ pub fn do_secp256k1_recover_pubkey< } } +/// Return code (error code) for a valid signature +const SECP256R1_VERIFY_CODE_VALID: u32 = 0; + +/// Return code (error code) for an invalid signature +const SECP256R1_VERIFY_CODE_INVALID: u32 = 1; + +pub fn do_secp256r1_verify( + mut env: FunctionEnvMut>, + hash_ptr: u32, + signature_ptr: u32, + pubkey_ptr: u32, +) -> VmResult { + let (data, mut store) = env.data_and_store_mut(); + + let hash = read_region(&data.memory(&mut store), hash_ptr, MESSAGE_HASH_MAX_LEN)?; + let signature = read_region(&data.memory(&mut store), signature_ptr, ECDSA_SIGNATURE_LEN)?; + let pubkey = read_region(&data.memory(&mut store), pubkey_ptr, ECDSA_PUBKEY_MAX_LEN)?; + + let gas_info = GasInfo::with_cost(data.gas_config.secp256r1_verify_cost); + process_gas_info(data, &mut store, gas_info)?; + let result = secp256r1_verify(&hash, &signature, &pubkey); + let code = match result { + Ok(valid) => { + if valid { + SECP256R1_VERIFY_CODE_VALID + } else { + SECP256R1_VERIFY_CODE_INVALID + } + } + Err(err) => match err { + CryptoError::InvalidHashFormat { .. } + | CryptoError::InvalidPubkeyFormat { .. } + | CryptoError::InvalidSignatureFormat { .. } + | CryptoError::GenericErr { .. } => err.code(), + CryptoError::BatchErr { .. } | CryptoError::InvalidRecoveryParam { .. } => { + panic!("Error must not happen for this call") + } + }, + }; + Ok(code) +} + /// Return code (error code) for a valid signature const ED25519_VERIFY_CODE_VALID: u32 = 0; @@ -682,6 +725,7 @@ mod tests { "addr_humanize" => Function::new_typed(&mut store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "secp256k1_recover_pubkey" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }), + "secp256r1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "ed25519_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "ed25519_batch_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "debug" => Function::new_typed(&mut store, |_a: u32| {}), @@ -1691,6 +1735,285 @@ mod tests { assert_eq!(force_read(&mut fe_mut, pubkey_ptr), expected); } + #[test] + fn do_secp256r1_verify_works() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256r1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 0 + ); + } + + #[test] + fn do_secp256r1_verify_wrong_hash_verify_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let mut hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + // alter hash + hash[0] ^= 0x01; + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 1 + ); + } + + #[test] + fn do_secp256r1_verify_larger_hash_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let mut hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + // extend / break hash + hash.push(0x00); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + let result = do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr); + match result.unwrap_err() { + VmError::CommunicationErr { + source: CommunicationError::RegionLengthTooBig { length, .. }, + .. + } => assert_eq!(length, MESSAGE_HASH_MAX_LEN + 1), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn do_secp256r1_verify_shorter_hash_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let mut hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + // reduce / break hash + hash.pop(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 3 // mapped InvalidHashFormat + ); + } + + #[test] + fn do_secp256r1_verify_wrong_sig_verify_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let mut sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + // alter sig + sig[0] ^= 0x01; + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 1 + ); + } + + #[test] + fn do_secp256r1_verify_larger_sig_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let mut sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + // extend / break sig + sig.push(0x00); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + let result = do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr); + match result.unwrap_err() { + VmError::CommunicationErr { + source: CommunicationError::RegionLengthTooBig { length, .. }, + .. + } => assert_eq!(length, ECDSA_SIGNATURE_LEN + 1), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn do_secp256r1_verify_shorter_sig_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let mut sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + // reduce / break sig + sig.pop(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 4 // mapped InvalidSignatureFormat + ) + } + + #[test] + fn do_secp256r1_verify_wrong_pubkey_format_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let mut pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + // alter pubkey format + pubkey[0] ^= 0x01; + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 5 // mapped InvalidPubkeyFormat + ) + } + + #[test] + fn do_secp256r1_verify_wrong_pubkey_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let mut pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + // alter pubkey + pubkey[1] ^= 0x01; + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 10 // mapped GenericErr + ) + } + + #[test] + fn do_secp256r1_verify_larger_pubkey_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let mut pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + // extend / break pubkey + pubkey.push(0x00); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + let result = do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr); + match result.unwrap_err() { + VmError::CommunicationErr { + source: CommunicationError::RegionLengthTooBig { length, .. }, + .. + } => assert_eq!(length, ECDSA_PUBKEY_MAX_LEN + 1), + e => panic!("Unexpected error: {e:?}"), + } + } + + #[test] + fn do_secp256r1_verify_shorter_pubkey_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let mut pubkey = hex::decode(ECDSA_PUBKEY_HEX).unwrap(); + // reduce / break pubkey + pubkey.pop(); + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 5 // mapped InvalidPubkeyFormat + ) + } + + #[test] + fn do_secp256r1_verify_empty_pubkey_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = hex::decode(ECDSA_HASH_HEX).unwrap(); + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = hex::decode(ECDSA_SIG_HEX).unwrap(); + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = vec![]; + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 5 // mapped InvalidPubkeyFormat + ) + } + + #[test] + fn do_secp256r1_verify_wrong_data_fails() { + let api = MockApi::default(); + let (fe, mut store, mut _instance) = make_instance(api); + let mut fe_mut = fe.into_mut(&mut store); + + let hash = vec![0x22; MESSAGE_HASH_MAX_LEN]; + let hash_ptr = write_data(&mut fe_mut, &hash); + let sig = vec![0x22; ECDSA_SIGNATURE_LEN]; + let sig_ptr = write_data(&mut fe_mut, &sig); + let pubkey = vec![0x04; ECDSA_PUBKEY_MAX_LEN]; + let pubkey_ptr = write_data(&mut fe_mut, &pubkey); + + assert_eq!( + do_secp256k1_verify(fe_mut, hash_ptr, sig_ptr, pubkey_ptr).unwrap(), + 10 // mapped GenericErr + ) + } + #[test] fn do_ed25519_verify_works() { let api = MockApi::default(); diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index cd9efe65de..0572ce8073 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -16,7 +16,7 @@ use crate::errors::{CommunicationError, VmError, VmResult}; use crate::imports::{ do_abort, do_addr_canonicalize, do_addr_humanize, do_addr_validate, do_db_read, do_db_remove, do_db_write, do_debug, do_ed25519_batch_verify, do_ed25519_verify, do_query_chain, - do_secp256k1_recover_pubkey, do_secp256k1_verify, + do_secp256k1_recover_pubkey, do_secp256k1_verify, do_secp256r1_verify, }; #[cfg(feature = "iterator")] use crate::imports::{do_db_next, do_db_next_key, do_db_next_value, do_db_scan}; @@ -174,6 +174,14 @@ where Function::new_typed_with_env(&mut store, &fe, do_secp256k1_recover_pubkey), ); + // Verifies message hashes against a signature with a public key, using the secp256r1 ECDSA parametrization. + // Returns 0 on verification success, 1 on verification failure, and values greater than 1 in case of error. + // Ownership of input pointers is not transferred to the host. + env_imports.insert( + "secp256r1_verify", + Function::new_typed_with_env(&mut store, &fe, do_secp256r1_verify), + ); + // Verifies a message against a signature with a public key, using the ed25519 EdDSA scheme. // Returns 0 on verification success, 1 on verification failure, and values greater than 1 in case of error. // Ownership of input pointers is not transferred to the host.