diff --git a/Cargo.lock b/Cargo.lock index f0235d82fbf..5f619f40914 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1719,20 +1719,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if 1.0.0", - "ecdsa", - "elliptic-curve", - "once_cell", - "serdect", - "sha2 0.10.8", -] - [[package]] name = "keccak" version = "0.1.5" @@ -1814,6 +1800,7 @@ dependencies = [ "base64 0.22.1", "digest 0.9.0", "hmac-drbg", + "lazy_static", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -3332,14 +3319,15 @@ dependencies = [ "ed25519-dalek", "getrandom 0.2.12", "hashbrown 0.15.2", - "k256", "lazy_static", + "libsecp256k1", "nix", "p256", "proptest", "rand 0.8.5", "ripemd", "rusqlite", + "secp256k1", "serde", "serde_derive", "serde_json", diff --git a/clarity/fuzz/Cargo.lock b/clarity/fuzz/Cargo.lock index aaef48b3751..67358ac680a 100644 --- a/clarity/fuzz/Cargo.lock +++ b/clarity/fuzz/Cargo.lock @@ -50,6 +50,12 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "autocfg" version = "1.5.0" @@ -62,6 +68,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.8.0" @@ -80,6 +92,15 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -187,6 +208,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -209,6 +236,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -218,7 +255,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "serde", @@ -255,13 +292,22 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -295,7 +341,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "serdect", @@ -320,7 +366,7 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", - "sha2", + "sha2 0.10.9", "subtle", ] @@ -332,7 +378,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -560,13 +606,34 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", ] [[package]] @@ -639,20 +706,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "serdect", - "sha2", -] - [[package]] name = "keccak" version = "0.1.5" @@ -694,6 +747,55 @@ dependencies = [ "libc", ] +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "lazy_static", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libsqlite3-sys" version = "0.28.0" @@ -760,6 +862,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "p256" version = "0.13.2" @@ -770,7 +878,7 @@ dependencies = [ "elliptic-curve", "primeorder", "serdect", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -935,7 +1043,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] @@ -945,7 +1053,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1037,6 +1145,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "1.0.26" @@ -1095,6 +1222,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1103,7 +1243,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", "sha2-asm", ] @@ -1122,7 +1262,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -1138,7 +1278,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] @@ -1204,17 +1344,18 @@ dependencies = [ "curve25519-dalek", "ed25519-dalek", "hashbrown 0.15.5", - "k256", "lazy_static", + "libsecp256k1", "nix", "p256", "rand", "ripemd", "rusqlite", + "secp256k1", "serde", "serde_derive", "serde_json", - "sha2", + "sha2 0.10.9", "sha3", "slog", "slog-term", diff --git a/stacks-common/Cargo.toml b/stacks-common/Cargo.toml index 00f808c5eec..b9944732c98 100644 --- a/stacks-common/Cargo.toml +++ b/stacks-common/Cargo.toml @@ -45,7 +45,6 @@ slog-term = { version = "2.6.0", default-features = false } thiserror = { workspace = true } # RustCrypto elliptic curve crates -k256 = { version = "0.13", default-features = false, features = ["std", "serde", "ecdsa"] } p256 = { version = "0.13", default-features = false, features = ["std", "serde", "ecdsa"] } # Optional dependencies @@ -65,8 +64,12 @@ winapi = { version = "0.3", features = [ ], optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] +secp256k1 = { version = "0.24.3", default-features = false, features = ["std","serde", "recovery"] } rusqlite = { workspace = true, optional = true } +[target.'cfg(target_family = "wasm")'.dependencies] +libsecp256k1 = { version = "0.7.2", default-features = false, features = ["hmac", "lazy-static-context"] } + [target.'cfg(all(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"), not(any(target_os="windows"))))'.dependencies] sha2 = { version = "0.10", features = ["asm"] } @@ -99,5 +102,5 @@ bech32_std = [] bech32_strict = [] # Wasm-specific features for easier configuration -wasm-web = ["rand", "getrandom/js"] +wasm-web = ["rand", "getrandom/js", "libsecp256k1/static-context"] wasm-deterministic = ["getrandom/custom"] diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index 8dd2e381451..fafe84a6c32 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -69,6 +69,12 @@ pub trait PublicKey: Clone + fmt::Debug + serde::Serialize + serde::de::Deserial pub trait PrivateKey: Clone + fmt::Debug + serde::Serialize + serde::de::DeserializeOwned { fn to_bytes(&self) -> Vec; fn sign(&self, data_hash: &[u8]) -> Result; + #[cfg(any(test, feature = "testing"))] + fn sign_with_noncedata( + &self, + data_hash: &[u8], + noncedata: &[u8; 32], + ) -> Result; } pub trait Address: Clone + fmt::Debug + fmt::Display { diff --git a/stacks-common/src/util/pipe.rs b/stacks-common/src/util/pipe.rs index f5ccafc1247..28050461441 100644 --- a/stacks-common/src/util/pipe.rs +++ b/stacks-common/src/util/pipe.rs @@ -315,7 +315,7 @@ mod test { #[test] fn test_connection_pipe_oneshot() { - let tests = vec![ + let tests = [ // .0: the list of vecs to send to the writer // .1: the number of bytes to read each time // .2: the expected Result diff --git a/stacks-common/src/util/secp256k1.rs b/stacks-common/src/util/secp256k1.rs deleted file mode 100644 index 694d414b7ce..00000000000 --- a/stacks-common/src/util/secp256k1.rs +++ /dev/null @@ -1,1015 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2025 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::hash::{Hash, Hasher}; - -use k256::ecdsa::signature::hazmat::PrehashVerifier; -use k256::ecdsa::{ - RecoveryId as K256RecoveryId, Signature as K256Signature, SigningKey as K256SigningKey, - VerifyingKey as K256VerifyingKey, -}; -use k256::elliptic_curve::generic_array::GenericArray; -use k256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; -use k256::{EncodedPoint, PublicKey as K256PublicKey, SecretKey as K256SecretKey}; -use thiserror::Error; - -use crate::types::{PrivateKey, PublicKey}; -use crate::util::hash::{hex_bytes, to_hex, Sha256Sum}; - -pub const MESSAGE_SIGNATURE_ENCODED_SIZE: u32 = 65; - -pub struct MessageSignature(pub [u8; 65]); -impl_array_newtype!(MessageSignature, u8, 65); -impl_array_hexstring_fmt!(MessageSignature); -impl_byte_array_newtype!(MessageSignature, u8, 65); -impl_byte_array_serde!(MessageSignature); - -pub struct SchnorrSignature(pub [u8; 65]); -impl_array_newtype!(SchnorrSignature, u8, 65); -impl_array_hexstring_fmt!(SchnorrSignature); -impl_byte_array_newtype!(SchnorrSignature, u8, 65); -impl_byte_array_serde!(SchnorrSignature); -pub const SCHNORR_SIGNATURE_ENCODED_SIZE: u32 = 65; - -impl Default for SchnorrSignature { - /// Creates a default Schnorr Signature. Note this is not a valid signature. - fn default() -> Self { - Self([0u8; 65]) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Error)] -pub enum Secp256k1Error { - #[error("Invalid key")] - InvalidKey, - #[error("Invalid signature")] - InvalidSignature, - #[error("Invalid message")] - InvalidMessage, - #[error("Invalid recovery ID")] - InvalidRecoveryId, - #[error("Signing failed")] - SigningFailed, - #[error("Recovery failed")] - RecoveryFailed, -} - -/// An ECDSA recoverable signature, which includes the recovery ID. -pub struct RecoverableSignature { - signature: K256Signature, - recovery_id: K256RecoveryId, -} - -impl RecoverableSignature { - /// Converts a recoverable signature to a non-recoverable one. - pub fn to_standard(&self) -> SignatureCompat { - SignatureCompat { - signature: self.signature, - } - } - - /// Serializes the signature in compact format. - pub fn serialize_compact(&self) -> (K256RecoveryId, [u8; 64]) { - (self.recovery_id, self.signature.to_bytes().into()) - } -} - -/// Compatibility wrapper to provide missing methods. -pub struct SignatureCompat { - signature: K256Signature, -} - -impl SignatureCompat { - /// Serializes the signature in DER format. - pub fn serialize_der(&self) -> Vec { - self.signature.to_der().as_bytes().to_vec() - } -} - -impl From<(K256Signature, K256RecoveryId)> for RecoverableSignature { - fn from((signature, recovery_id): (K256Signature, K256RecoveryId)) -> Self { - RecoverableSignature { - signature, - recovery_id, - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Secp256k1PublicKey { - key: K256VerifyingKey, - compressed: bool, -} -impl_byte_array_serde!(Secp256k1PublicKey); - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Secp256k1PrivateKey { - key: K256SigningKey, - compress_public: bool, -} -impl_byte_array_serde!(Secp256k1PrivateKey); - -impl Hash for Secp256k1PublicKey { - fn hash(&self, state: &mut H) { - // Hash based on the compressed public key bytes for consistency - self.to_bytes_compressed().hash(state); - } -} - -impl MessageSignature { - /// Creates an "empty" signature (all zeros). Note this is not a valid signature. - pub fn empty() -> MessageSignature { - // NOTE: this cannot be a valid signature - MessageSignature([0u8; 65]) - } - - /// Generates place-holder data (for testing purposes only). - #[cfg(any(test, feature = "testing"))] - pub fn from_raw(sig: &[u8]) -> MessageSignature { - let mut buf = [0u8; 65]; - for (dst, src) in buf.iter_mut().zip(sig.iter().copied()) { - *dst = src; - } - MessageSignature(buf) - } - - /// Converts from a secp256k1::ecdsa::RecoverableSignature to our MessageSignature. - pub fn from_secp256k1_recoverable(sig: &RecoverableSignature) -> MessageSignature { - let (recid, bytes) = sig.serialize_compact(); - let mut ret_bytes = [0u8; 65]; - if let Some((first, rest)) = ret_bytes.split_first_mut() { - *first = recid.to_byte(); - rest.copy_from_slice(&bytes); - } - MessageSignature(ret_bytes) - } - - /// Converts to a secp256k1::ecdsa::RecoverableSignature. - pub fn to_secp256k1_recoverable(&self) -> Option { - let (recid_byte, sig_bytes) = self.0.split_first()?; - let recovery_id = K256RecoveryId::from_byte(*recid_byte)?; - let signature = K256Signature::from_slice(sig_bytes).ok()?; - Some(RecoverableSignature { - signature, - recovery_id, - }) - } - - /// Converts from VRS to RSV. - pub fn to_rsv(&self) -> Vec { - let mut out = self.0; - out.rotate_left(1); - out.to_vec() - } -} - -impl Secp256k1PublicKey { - /// Generates a new random public key (for testing purposes only). - #[cfg(any(test, feature = "testing"))] - pub fn new() -> Secp256k1PublicKey { - Secp256k1PublicKey::from_private(&Secp256k1PrivateKey::random()) - } - - /// Creates a Secp256k1PublicKey from a hex string representation. - pub fn from_hex(hex_string: &str) -> Result { - let data = hex_bytes(hex_string).map_err(|_e| "Failed to decode hex public key")?; - Secp256k1PublicKey::from_slice(&data[..]).map_err(|_e| "Invalid public key hex string") - } - - /// Creates a Secp256k1PublicKey from a byte slice. - pub fn from_slice(data: &[u8]) -> Result { - let encoded_point = EncodedPoint::from_bytes(data) - .map_err(|_| "Invalid public key: failed to parse encoded point")?; - - let public_key = - Option::::from(K256PublicKey::from_encoded_point(&encoded_point)) - .ok_or("Invalid public key: failed to decode point")?; - - let verifying_key = K256VerifyingKey::from(public_key); - - Ok(Secp256k1PublicKey { - key: verifying_key, - compressed: data.len() == 33, // 33 bytes = compressed, 65 bytes = uncompressed - }) - } - - /// Creates a Secp256k1PublicKey from a Secp256k1PrivateKey. - pub fn from_private(privk: &Secp256k1PrivateKey) -> Secp256k1PublicKey { - let verifying_key = privk.key.verifying_key(); - Secp256k1PublicKey { - key: *verifying_key, - compressed: privk.compress_public, - } - } - - /// Converts the public key to a hex string representation. - pub fn to_hex(&self) -> String { - to_hex(&self.to_bytes()) - } - - /// Converts the public key to a compressed byte representation. - pub fn to_bytes_compressed(&self) -> Vec { - let public_key = K256PublicKey::from(&self.key); - let encoded_point = public_key.to_encoded_point(true); // true = compressed - encoded_point.as_bytes().to_vec() - } - - /// Returns whether the public key is in compressed format. - pub fn compressed(&self) -> bool { - self.compressed - } - - /// Sets whether the public key should be in compressed format when serialized. - pub fn set_compressed(&mut self, value: bool) { - self.compressed = value; - } - - /// Recovers message and signature to public key (will be compressed). - pub fn recover_to_pubkey( - msg: &[u8], - sig: &MessageSignature, - ) -> Result { - if msg.len() != 32 { - return Err("Invalid message: failed to decode data hash: must be a 32-byte hash"); - } - - let recoverable_sig = sig - .to_secp256k1_recoverable() - .ok_or("Invalid signature: failed to decode recoverable signature")?; - - let recovered_key = K256VerifyingKey::recover_from_prehash( - msg, - &recoverable_sig.signature, - recoverable_sig.recovery_id, - ) - .map_err(|_| "Invalid signature: failed to recover public key")?; - - Ok(Secp256k1PublicKey { - key: recovered_key, - compressed: true, - }) - } - - // For benchmarking - #[cfg(test)] - pub fn recover_benchmark( - msg: &[u8; 32], - sig: &RecoverableSignature, - ) -> Result { - K256VerifyingKey::recover_from_prehash(msg, &sig.signature, sig.recovery_id) - .map_err(|_| "Invalid signature: failed to recover public key") - } -} - -#[cfg(any(test, feature = "testing"))] -impl Default for Secp256k1PublicKey { - fn default() -> Self { - Self::new() - } -} - -impl PublicKey for Secp256k1PublicKey { - /// Converts the public key to a byte representation. - fn to_bytes(&self) -> Vec { - let public_key = K256PublicKey::from(&self.key); - let encoded_point = public_key.to_encoded_point(self.compressed); - encoded_point.as_bytes().to_vec() - } - - /// Verifies a signature against the public key. - fn verify(&self, data_hash: &[u8], sig: &MessageSignature) -> Result { - if data_hash.len() != 32 { - return Err("Invalid message: failed to decode data hash: must be a 32-byte hash"); - } - - let recoverable_sig = sig - .to_secp256k1_recoverable() - .ok_or("Invalid signature: failed to decode recoverable signature")?; - - let recovered_pubkey = K256VerifyingKey::recover_from_prehash( - data_hash, - &recoverable_sig.signature, - recoverable_sig.recovery_id, - ) - .map_err(|_| "Invalid signature: failed to recover public key")?; - - if recovered_pubkey != self.key { - test_debug!("{:?} != {:?}", &recovered_pubkey, &self.key); - return Ok(false); - } - - // Verify the signature is normalized (low-S) - if recoverable_sig.signature.normalize_s().is_some() { - return Err("Invalid signature: high-S"); - } - - Ok(true) - } -} - -impl Secp256k1PrivateKey { - /// Generates a new random private key. - #[cfg(feature = "rand")] - pub fn random() -> Secp256k1PrivateKey { - let secret_key = K256SecretKey::random(&mut rand::thread_rng()); - let signing_key = K256SigningKey::from(secret_key); - Secp256k1PrivateKey { - key: signing_key, - compress_public: true, - } - } - - /// Creates a new Secp256k1PrivateKey. - #[cfg(feature = "rand")] - pub fn new() -> Secp256k1PrivateKey { - Self::random() - } - - /// Creates a Secp256k1PrivateKey from seed bytes by repeatedly - /// SHA256 hashing the seed bytes until a private key is found. - /// - /// If `seed` is a valid private key, it will be returned without hashing. - /// The returned private key's compress_public flag will be `true`. - pub fn from_seed(seed: &[u8]) -> Secp256k1PrivateKey { - let mut re_hashed_seed = Vec::from(seed); - loop { - if let Ok(mut sk) = Secp256k1PrivateKey::from_slice(&re_hashed_seed[..]) { - // set this to true: LocalPeer will be doing this anyways, - // and that's currently the only way this method is used - sk.set_compress_public(true); - return sk; - } else { - re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..]) - .as_bytes() - .to_vec() - } - } - } - - /// Creates a Secp256k1PrivateKey from a hex string representation. - pub fn from_hex(hex_string: &str) -> Result { - let data = hex_bytes(hex_string).map_err(|_e| "Failed to decode hex private key")?; - Secp256k1PrivateKey::from_slice(&data[..]).map_err(|_e| "Invalid private key hex string") - } - - /// Creates a Secp256k1PrivateKey from a byte slice. - pub fn from_slice(data: &[u8]) -> Result { - if data.len() < 32 { - return Err("Invalid private key: shorter than 32 bytes"); - } - if data.len() > 33 { - return Err("Invalid private key: greater than 33 bytes"); - } - let compress_public = if data.len() == 33 { - // compressed byte tag? - if data[32] != 0x01 { - return Err("Invalid private key: invalid compressed byte marker"); - } - true - } else { - false - }; - - let mut key_bytes = [0u8; 32]; - key_bytes.copy_from_slice(&data[0..32]); - - let secret_key = K256SecretKey::from_bytes(&GenericArray::from(key_bytes)) - .map_err(|_| "Invalid private key: failed to load")?; - let signing_key = K256SigningKey::from(secret_key); - - Ok(Secp256k1PrivateKey { - key: signing_key, - compress_public, - }) - } - - /// Returns whether the corresponding public key should be in compressed format when - /// serialized. - pub fn compress_public(&self) -> bool { - self.compress_public - } - - /// Sets whether the corresponding public key should be in compressed format when serialized. - pub fn set_compress_public(&mut self, value: bool) { - self.compress_public = value; - } - - /// Converts the private key to a hex string representation. - pub fn to_hex(&self) -> String { - let mut bytes = self.key.to_bytes().to_vec(); - if self.compress_public { - bytes.push(1); - } - to_hex(&bytes) - } - - /// Converts the private key to a 32-byte array representation. - pub fn as_slice(&self) -> [u8; 32] { - self.key.to_bytes().into() - } -} - -#[cfg(feature = "rand")] -impl Default for Secp256k1PrivateKey { - fn default() -> Self { - Self::new() - } -} - -impl PrivateKey for Secp256k1PrivateKey { - /// Converts the private key to a byte representation. - fn to_bytes(&self) -> Vec { - let mut bits = self.key.to_bytes().to_vec(); - if self.compress_public { - bits.push(0x01); - } - bits - } - - /// Signs a message hash with the private key, producing a recoverable signature. - fn sign(&self, data_hash: &[u8]) -> Result { - if data_hash.len() != 32 { - return Err("Invalid message: failed to decode data hash: must be a 32-byte hash"); - } - - let (signature, recovery_id) = self - .key - .sign_prehash_recoverable(data_hash) - .map_err(|_| "Signing failed")?; - - let recoverable_sig = RecoverableSignature { - signature, - recovery_id, - }; - - Ok(MessageSignature::from_secp256k1_recoverable( - &recoverable_sig, - )) - } -} - -/// Recovers a public key from a message hash and a recoverable signature. -/// The returned public key is in compressed format (33 bytes). -pub fn secp256k1_recover( - message_arr: &[u8], - serialized_signature_arr: &[u8], -) -> Result<[u8; 33], Secp256k1Error> { - if message_arr.len() != 32 { - return Err(Secp256k1Error::InvalidMessage); - } - - if serialized_signature_arr.len() < 65 { - return Err(Secp256k1Error::InvalidSignature); - } - - let recovery_id = K256RecoveryId::from_byte(serialized_signature_arr[64]) - .ok_or(Secp256k1Error::InvalidRecoveryId)?; - - let signature = K256Signature::from_slice(&serialized_signature_arr[..64]) - .map_err(|_| Secp256k1Error::InvalidSignature)?; - - let recovered_pub = - K256VerifyingKey::recover_from_prehash(message_arr, &signature, recovery_id) - .map_err(|_| Secp256k1Error::RecoveryFailed)?; - - let public_key = K256PublicKey::from(&recovered_pub); - let encoded_point = public_key.to_encoded_point(true); // compressed - let mut result = [0u8; 33]; - result.copy_from_slice(encoded_point.as_bytes()); - - Ok(result) -} - -/// Verifies a message hash against a signature and a public key. -pub fn secp256k1_verify( - message_arr: &[u8], - serialized_signature_arr: &[u8], - pubkey_arr: &[u8], -) -> Result<(), Secp256k1Error> { - if message_arr.len() != 32 { - return Err(Secp256k1Error::InvalidMessage); - } - - if serialized_signature_arr.len() < 64 { - return Err(Secp256k1Error::InvalidSignature); - } - - let encoded_point = - EncodedPoint::from_bytes(pubkey_arr).map_err(|_| Secp256k1Error::InvalidKey)?; - - let public_key = - Option::::from(K256PublicKey::from_encoded_point(&encoded_point)) - .ok_or(Secp256k1Error::InvalidKey)?; - let verifying_key = K256VerifyingKey::from(public_key); - - let signature = K256Signature::from_slice(&serialized_signature_arr[..64]) - .map_err(|_| Secp256k1Error::InvalidSignature)?; - - verifying_key - .verify_prehash(message_arr, &signature) - .map_err(|_| Secp256k1Error::InvalidSignature) -} - -#[cfg(test)] -mod tests { - use rand::RngCore; - - use super::*; - use crate::util::get_epoch_time_ms; - - struct KeyFixture { - input: I, - result: R, - } - - #[derive(Debug)] - struct VerifyFixture { - public_key: &'static str, - data: &'static str, - signature: &'static str, - result: R, - } - - fn verifying_key_from_hex(hex: &str) -> K256VerifyingKey { - let bytes = hex_bytes(hex).unwrap(); - K256VerifyingKey::from_sec1_bytes(&bytes).expect("valid verifying key bytes") - } - - #[test] - fn to_rsv_rotates_recovery_byte() { - let mut bytes = [0u8; 65]; - for (idx, slot) in bytes.iter_mut().enumerate() { - *slot = idx as u8; - } - let sig = MessageSignature(bytes); - - let rsv = sig.to_rsv(); - - assert_eq!(rsv.len(), 65); - assert_eq!(rsv[0], 1, "R should start where VRS's R begins"); - assert_eq!(rsv[63], 64, "S should end with last signature byte"); - assert_eq!(rsv[64], 0, "V should move to the tail after rotation"); - } - - #[test] - fn test_privkey_parse_serialize_compressed() { - let mut t1 = Secp256k1PrivateKey::random(); - t1.set_compress_public(true); - let h_comp = t1.to_hex(); - let json_comp = serde_json::to_string(&t1).unwrap(); - assert_eq!(json_comp, format!("\"{h_comp}\"")); - let deser_comp: Secp256k1PrivateKey = serde_json::from_str(&json_comp).unwrap(); - assert_eq!(deser_comp.to_hex(), h_comp); - assert!(deser_comp.compress_public()); - - t1.set_compress_public(false); - let h_uncomp = t1.to_hex(); - let json_uncomp = serde_json::to_string(&t1).unwrap(); - assert_eq!(json_uncomp, format!("\"{h_uncomp}\"")); - let deser_uncomp: Secp256k1PrivateKey = serde_json::from_str(&json_uncomp).unwrap(); - assert_eq!(deser_uncomp.to_hex(), h_uncomp); - assert!(!deser_uncomp.compress_public()); - - assert!(h_comp != h_uncomp); - assert_eq!(h_comp.len(), 66); - assert_eq!(h_uncomp.len(), 64); - - let (uncomp, comp_value) = h_comp.split_at(64); - assert_eq!(comp_value, "01"); - assert_eq!(uncomp, &h_uncomp); - - assert!(Secp256k1PrivateKey::from_hex(&h_comp) - .unwrap() - .compress_public()); - assert!(!Secp256k1PrivateKey::from_hex(&h_uncomp) - .unwrap() - .compress_public()); - - assert_eq!(Secp256k1PrivateKey::from_hex(&h_uncomp), Ok(t1.clone())); - - t1.set_compress_public(true); - - assert_eq!(Secp256k1PrivateKey::from_hex(&h_comp), Ok(t1)); - } - - #[test] - fn test_pubkey_parse_serialize_compressed() { - let privk = Secp256k1PrivateKey::random(); - let mut pubk = Secp256k1PublicKey::from_private(&privk); - - pubk.set_compressed(true); - let h_comp = pubk.to_hex(); - let json_comp = serde_json::to_string(&pubk).unwrap(); - assert_eq!(json_comp, format!("\"{}\"", h_comp)); - let deser_comp: Secp256k1PublicKey = serde_json::from_str(&json_comp).unwrap(); - assert_eq!(deser_comp.to_hex(), h_comp); - assert!(deser_comp.compressed()); - - pubk.set_compressed(false); - let h_uncomp = pubk.to_hex(); - let json_uncomp = serde_json::to_string(&pubk).unwrap(); - assert_eq!(json_uncomp, format!("\"{}\"", h_uncomp)); - let deser_uncomp: Secp256k1PublicKey = serde_json::from_str(&json_uncomp).unwrap(); - assert_eq!(deser_uncomp.to_hex(), h_uncomp); - assert!(!deser_uncomp.compressed()); - - assert!(h_comp != h_uncomp); - assert_eq!(h_comp.len(), 66); - assert_eq!(h_uncomp.len(), 130); - - assert!(Secp256k1PublicKey::from_hex(&h_comp).unwrap().compressed()); - assert!(!Secp256k1PublicKey::from_hex(&h_uncomp) - .unwrap() - .compressed()); - - assert_eq!(Secp256k1PublicKey::from_hex(&h_uncomp), Ok(pubk.clone())); - - pubk.set_compressed(true); - - assert_eq!(Secp256k1PublicKey::from_hex(&h_comp), Ok(pubk)); - } - - #[test] - /// Test the behavior of from_seed using hard-coded values from previous existing integration tests - fn sk_from_seed() { - let sk = Secp256k1PrivateKey::from_seed(&[2; 32]); - assert_eq!( - Secp256k1PublicKey::from_private(&sk).to_hex(), - "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766" - ); - assert_eq!( - sk.to_hex(), - "020202020202020202020202020202020202020202020202020202020202020201" - ); - - let sk = Secp256k1PrivateKey::from_seed(&[0]); - assert_eq!( - Secp256k1PublicKey::from_private(&sk).to_hex(), - "0243311589af63c2adda04fcd7792c038a05c12a4fe40351b3eb1612ff6b2e5a0e" - ); - assert_eq!( - sk.to_hex(), - "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d01" - ); - } - - #[test] - fn test_parse_serialize() { - let fixtures = vec![ - KeyFixture { - input: "0233d78f74de8ef4a1de815b6d5c5c129c073786305c0826c499b1811c9a12cee5", - result: Some(Secp256k1PublicKey { - key: verifying_key_from_hex("0233d78f74de8ef4a1de815b6d5c5c129c073786305c0826c499b1811c9a12cee5"), - compressed: true - }) - }, - KeyFixture { - input: "044a83ad59dbae1e2335f488dbba5f8604d00f612a43ebaae784b5b7124cc38c3aaf509362787e1a8e25131724d57fec81b87889aabb4edf7bd89f5c4daa4f8aa7", - result: Some(Secp256k1PublicKey { - key: verifying_key_from_hex("044a83ad59dbae1e2335f488dbba5f8604d00f612a43ebaae784b5b7124cc38c3aaf509362787e1a8e25131724d57fec81b87889aabb4edf7bd89f5c4daa4f8aa7"), - compressed: false - }) - }, - KeyFixture { - input: "0233d78f74de8ef4a1de815b6d5c5c129c073786305c0826c499b1811c9a12ce", - result: None, - }, - KeyFixture { - input: "044a83ad59dbae1e2335f488dbba5f8604d00f612a43ebaae784b5b7124cc38c3aaf509362787e1a8e25131724d57fec81b87889aabb4edf7bd89f5c4daa4f8a", - result: None, - } - ]; - - for fixture in fixtures { - let key_res = Secp256k1PublicKey::from_hex(fixture.input); - match (key_res, fixture.result) { - (Ok(key), Some(key_result)) => { - assert_eq!(key, key_result); - - let key_from_slice = - Secp256k1PublicKey::from_slice(&hex_bytes(fixture.input).unwrap()[..]) - .unwrap(); - assert_eq!(key_from_slice, key_result); - - let key_bytes = key.to_bytes(); - assert_eq!(key_bytes, hex_bytes(fixture.input).unwrap()); - } - (Err(_e), None) => {} - (_, _) => { - // either got a key when we didn't expect one, or didn't get a key when we did - // expect one. - panic!("Unexpected result: we either got a key when we didn't expect one, or didn't get a key when we did expect one."); - } - } - } - } - - #[test] - fn test_verify() { - let fixtures : Vec>> = vec![ - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Ok(true) - }, - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", - data: "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce", // sha256 hash of "nope" - result: Ok(false) - }, - VerifyFixture { - public_key: "034c35b09b758678165d6ed84a50b329900c99986cf8e9a358ceae0d03af91f5b6", // wrong key - signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Ok(false) - }, - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe7", // wrong sig (bad s) - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Ok(false) - }, - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "00454445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad r) - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Ok(false) - }, - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "01354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad recovery) - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Ok(false) - }, - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "02354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad recovery) - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Err("Invalid signature: failed to recover public key"), - }, - VerifyFixture { - public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", - signature: "03354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad recovery) - data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" - result: Err("Invalid signature: failed to recover public key"), - } - ]; - - for fixture in fixtures { - let key = Secp256k1PublicKey::from_hex(fixture.public_key).unwrap(); - let signature = MessageSignature::from_raw(&hex_bytes(fixture.signature).unwrap()); - let ver_res = key.verify(&hex_bytes(fixture.data).unwrap(), &signature); - match (ver_res, fixture.result) { - (Ok(true), Ok(true)) => {} - (Ok(false), Ok(false)) => {} - (Err(e1), Err(e2)) => assert_eq!(e1, e2), - (Err(e1), _) => { - test_debug!("Failed to verify signature: {}", e1); - panic!( - "failed fixture (verification: {:?}): {:#?}", - &ver_res, &fixture - ); - } - (_, _) => { - panic!( - "failed fixture (verification: {:?}): {:#?}", - &ver_res, &fixture - ); - } - } - } - } - - #[test] - #[ignore] - fn test_verify_benchmark_roundtrip() { - let mut runtime_sign = 0; - let mut runtime_verify = 0; - let mut runtime_recover = 0; - let mut rng = rand::thread_rng(); - - for i in 0..100 { - let privk = Secp256k1PrivateKey::random(); - let pubk = Secp256k1PublicKey::from_private(&privk); - - let mut msg = [0u8; 32]; - rng.fill_bytes(&mut msg); - - let sign_start = get_epoch_time_ms(); - for i in 0..1000 { - let sig = privk.sign(&msg).unwrap(); - } - let sign_end = get_epoch_time_ms(); - - let sig = privk.sign(&msg).unwrap(); - let recoverable_sig = sig.to_secp256k1_recoverable().unwrap(); - - let recovered_pubk = - Secp256k1PublicKey::recover_benchmark(&msg, &recoverable_sig).unwrap(); - assert_eq!(recovered_pubk, pubk.key); - - let recover_start = get_epoch_time_ms(); - for i in 0..1000 { - let recovered_pubk = - Secp256k1PublicKey::recover_benchmark(&msg, &recoverable_sig).unwrap(); - } - let recover_end = get_epoch_time_ms(); - - let verify_start = get_epoch_time_ms(); - for i in 0..1000 { - let valid = pubk.verify(&msg, &sig).unwrap(); - } - let verify_end = get_epoch_time_ms(); - - let valid = pubk.verify(&msg, &sig).unwrap(); - assert!(valid); - - test_debug!( - "Runtime: {:?} sign, {:?} recover, {:?} verify", - ((sign_end - sign_start) as f64) / 1000.0, - ((recover_end - recover_start) as f64) / 1000.0, - ((verify_end - verify_start) as f64) / 1000.0 - ); - - runtime_sign += sign_end - sign_start; - runtime_verify += verify_end - verify_start; - runtime_recover += recover_end - recover_start; - } - - test_debug!( - "Total Runtime: {:?} sign, {:?} verify, {:?} recover, {:?} verify - recover", - runtime_sign, - runtime_verify, - runtime_recover, - runtime_verify - runtime_recover - ); - } - - #[test] - fn test_from_seed() { - let sk = Secp256k1PrivateKey::from_seed(&[2; 32]); - let pubk = Secp256k1PublicKey::from_private(&sk); - - // Test that from_seed is deterministic - let sk2 = Secp256k1PrivateKey::from_seed(&[2; 32]); - let pubk2 = Secp256k1PublicKey::from_private(&sk2); - - assert_eq!(sk.to_hex(), sk2.to_hex()); - assert_eq!(pubk.to_hex(), pubk2.to_hex()); - } - - #[test] - fn test_roundtrip_sign_verify() { - let privk = Secp256k1PrivateKey::random(); - let pubk = Secp256k1PublicKey::from_private(&privk); - - let msg = b"hello world"; - let msg_hash = Sha256Sum::from_data(msg).as_bytes().to_vec(); - - let sig = privk.sign(&msg_hash).unwrap(); - let valid = pubk.verify(&msg_hash, &sig).unwrap(); - - assert!(valid); - } - - #[test] - fn test_verify_with_different_key() { - let privk1 = Secp256k1PrivateKey::random(); - let privk2 = Secp256k1PrivateKey::random(); - let pubk2 = Secp256k1PublicKey::from_private(&privk2); - - let msg = b"hello world"; - let msg_hash = Sha256Sum::from_data(msg).as_bytes().to_vec(); - - let sig = privk1.sign(&msg_hash).unwrap(); - let valid = pubk2.verify(&msg_hash, &sig).unwrap(); - - assert!(!valid); - } - - #[test] - fn test_public_key_compression() { - let privk = Secp256k1PrivateKey::random(); - let mut pubk = Secp256k1PublicKey::from_private(&privk); - - pubk.set_compressed(true); - let compressed_bytes = pubk.to_bytes(); - assert_eq!(compressed_bytes.len(), 33); - - pubk.set_compressed(false); - let uncompressed_bytes = pubk.to_bytes(); - assert_eq!(uncompressed_bytes.len(), 65); - - // Both should parse back to the same key - let pubk_from_compressed = Secp256k1PublicKey::from_slice(&compressed_bytes).unwrap(); - let pubk_from_uncompressed = Secp256k1PublicKey::from_slice(&uncompressed_bytes).unwrap(); - - assert_eq!(pubk_from_compressed.key, pubk_from_uncompressed.key); - } - - #[test] - fn test_recovery() { - let privk = Secp256k1PrivateKey::random(); - let pubk = Secp256k1PublicKey::from_private(&privk); - - let msg = b"hello world"; - let msg_hash = Sha256Sum::from_data(msg).as_bytes().to_vec(); - - let sig = privk.sign(&msg_hash).unwrap(); - let recovered_pubk = Secp256k1PublicKey::recover_to_pubkey(&msg_hash, &sig).unwrap(); - - // Both should have the same compressed public key bytes - assert_eq!( - pubk.to_bytes_compressed(), - recovered_pubk.to_bytes_compressed() - ); - } - - #[test] - fn test_high_s_signature() { - let privk = Secp256k1PrivateKey::random(); - let pubk = Secp256k1PublicKey::from_private(&privk); - - let msg = b"stacks secp256k1 high-s test____"; - let msg_hash = Sha256Sum::from_data(msg).as_bytes().to_vec(); - - // Sign the message - let sig = privk.sign(&msg_hash).unwrap(); - let pubkey_bytes = pubk.to_bytes(); - - // Get the underlying Signature to work with r,s components - let recoverable_sig = sig.to_secp256k1_recoverable().unwrap(); - - // Always get the low-s version first - let low_sig = if let Some(normalized) = recoverable_sig.signature.normalize_s() { - normalized // Original was high-s, use the normalized (low-s) version - } else { - recoverable_sig.signature // Original was already low-s - }; - - // Now create high-s version from the low-s signature - let (r, s) = (low_sig.r(), low_sig.s()); - let high_sig = { - // Make high-s by negating s (s' = -s mod n) - let s_hi = -(*s); - K256Signature::from_scalars(*r, s_hi).expect("valid (r, -s)") - }; - - let low_bytes = low_sig.to_bytes(); - let high_bytes = high_sig.to_bytes(); - - // Verify our assumptions about which is which - let low_is_low_s = low_sig.normalize_s().is_none(); - let high_is_high_s = high_sig.normalize_s().is_some(); - - assert!(low_is_low_s, "Low signature should be low-s"); - assert!(high_is_high_s, "High signature should be high-s"); - - // Low-s signature should pass verification - let low_result = secp256k1_verify(&msg_hash, &low_bytes, &pubkey_bytes); - assert!( - low_result.is_ok(), - "Low-s signature should pass verification" - ); - - // High-s signature should fail verification - let high_result = secp256k1_verify(&msg_hash, &high_bytes, &pubkey_bytes); - assert!( - high_result.is_err(), - "High-s signature should fail verification" - ); - - // Test normalization: high-s should pass when normalized to low-s - if let Some(normalized_sig) = high_sig.normalize_s() { - let normalized_bytes = normalized_sig.to_bytes(); - let normalized_result = secp256k1_verify(&msg_hash, &normalized_bytes, &pubkey_bytes); - assert!( - normalized_result.is_ok(), - "Normalized (low-s) signature should pass verification" - ); - - // The normalized signature should be the same as our low signature - assert_eq!( - normalized_bytes, low_bytes, - "Normalized signature should match our low signature" - ); - } else { - panic!("High-s signature should normalize to low-s"); - } - } -} diff --git a/stacks-common/src/util/secp256k1/mod.rs b/stacks-common/src/util/secp256k1/mod.rs new file mode 100644 index 00000000000..50ee281e306 --- /dev/null +++ b/stacks-common/src/util/secp256k1/mod.rs @@ -0,0 +1,33 @@ +#[cfg(not(target_family = "wasm"))] +mod native; + +#[cfg(not(target_family = "wasm"))] +pub use self::native::*; + +#[cfg(target_family = "wasm")] +mod wasm; + +#[cfg(target_family = "wasm")] +pub use self::wasm::*; + +pub const MESSAGE_SIGNATURE_ENCODED_SIZE: u32 = 65; + +pub struct MessageSignature(pub [u8; 65]); +impl_array_newtype!(MessageSignature, u8, 65); +impl_array_hexstring_fmt!(MessageSignature); +impl_byte_array_newtype!(MessageSignature, u8, 65); +impl_byte_array_serde!(MessageSignature); + +pub struct SchnorrSignature(pub [u8; 65]); +impl_array_newtype!(SchnorrSignature, u8, 65); +impl_array_hexstring_fmt!(SchnorrSignature); +impl_byte_array_newtype!(SchnorrSignature, u8, 65); +impl_byte_array_serde!(SchnorrSignature); +pub const SCHNORR_SIGNATURE_ENCODED_SIZE: u32 = 65; + +impl Default for SchnorrSignature { + /// Creates a default Schnorr Signature. Note this is not a valid signature. + fn default() -> Self { + Self([0u8; 65]) + } +} diff --git a/stacks-common/src/util/secp256k1/native.rs b/stacks-common/src/util/secp256k1/native.rs new file mode 100644 index 00000000000..e6b7452061d --- /dev/null +++ b/stacks-common/src/util/secp256k1/native.rs @@ -0,0 +1,714 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use ::secp256k1; +use ::secp256k1::ecdsa::{ + RecoverableSignature as LibSecp256k1RecoverableSignature, RecoveryId as LibSecp256k1RecoveryID, + Signature as LibSecp256k1Signature, +}; +pub use ::secp256k1::Error; +use ::secp256k1::{ + constants as LibSecp256k1Constants, Error as LibSecp256k1Error, Message as LibSecp256k1Message, + PublicKey as LibSecp256k1PublicKey, Secp256k1, SecretKey as LibSecp256k1PrivateKey, +}; +use serde::de::{Deserialize, Error as de_Error}; +use serde::Serialize; + +use super::MessageSignature; +use crate::types::{PrivateKey, PublicKey}; +use crate::util::hash::{hex_bytes, to_hex, Sha256Sum}; + +// per-thread Secp256k1 context +thread_local!(static _secp256k1: Secp256k1 = Secp256k1::new()); + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash)] +pub struct Secp256k1PublicKey { + // serde is broken for secp256k1, so do it ourselves + #[serde( + serialize_with = "secp256k1_pubkey_serialize", + deserialize_with = "secp256k1_pubkey_deserialize" + )] + key: LibSecp256k1PublicKey, + compressed: bool, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Secp256k1PrivateKey { + // serde is broken for secp256k1, so do it ourselves + #[serde( + serialize_with = "secp256k1_privkey_serialize", + deserialize_with = "secp256k1_privkey_deserialize" + )] + key: LibSecp256k1PrivateKey, + compress_public: bool, +} + +impl MessageSignature { + pub fn empty() -> MessageSignature { + // NOTE: this cannot be a valid signature + MessageSignature([0u8; 65]) + } + + #[cfg(any(test, feature = "testing"))] + // test method for generating place-holder data + pub fn from_raw(sig: &[u8]) -> MessageSignature { + let mut buf = [0u8; 65]; + if sig.len() < 65 { + buf.copy_from_slice(sig); + } else { + buf.copy_from_slice(&sig[..65]); + } + MessageSignature(buf) + } + + pub fn from_secp256k1_recoverable(sig: &LibSecp256k1RecoverableSignature) -> MessageSignature { + let (recid, bytes) = sig.serialize_compact(); + let mut ret_bytes = [0u8; 65]; + let recovery_id_byte = recid.to_i32() as u8; // recovery ID will be 0, 1, 2, or 3 + ret_bytes[0] = recovery_id_byte; + ret_bytes[1..=64].copy_from_slice(&bytes[..64]); + MessageSignature(ret_bytes) + } + + pub fn to_secp256k1_recoverable(&self) -> Option { + let recid = match LibSecp256k1RecoveryID::from_i32(self.0[0] as i32) { + Ok(rid) => rid, + Err(_) => { + return None; + } + }; + let mut sig_bytes = [0u8; 64]; + sig_bytes[..64].copy_from_slice(&self.0[1..=64]); + + LibSecp256k1RecoverableSignature::from_compact(&sig_bytes, recid).ok() + } + + /// Convert from VRS to RSV + pub fn to_rsv(&self) -> Vec { + [&self.0[1..], &self.0[0..1]].concat() + } +} + +#[cfg(any(test, feature = "testing"))] +impl Default for Secp256k1PublicKey { + fn default() -> Self { + Self::new() + } +} + +impl Secp256k1PublicKey { + #[cfg(any(test, feature = "testing"))] + pub fn new() -> Secp256k1PublicKey { + Secp256k1PublicKey::from_private(&Secp256k1PrivateKey::random()) + } + + pub fn from_hex(hex_string: &str) -> Result { + let data = hex_bytes(hex_string).map_err(|_e| "Failed to decode hex public key")?; + Secp256k1PublicKey::from_slice(&data[..]).map_err(|_e| "Invalid public key hex string") + } + + pub fn from_slice(data: &[u8]) -> Result { + match LibSecp256k1PublicKey::from_slice(data) { + Ok(pubkey_res) => Ok(Secp256k1PublicKey { + key: pubkey_res, + compressed: data.len() == LibSecp256k1Constants::PUBLIC_KEY_SIZE, + }), + Err(_e) => Err("Invalid public key: failed to load"), + } + } + + pub fn from_private(privk: &Secp256k1PrivateKey) -> Secp256k1PublicKey { + _secp256k1.with(|ctx| { + let pubk = LibSecp256k1PublicKey::from_secret_key(ctx, &privk.key); + Secp256k1PublicKey { + key: pubk, + compressed: privk.compress_public, + } + }) + } + + pub fn to_hex(&self) -> String { + to_hex(&self.to_bytes()) + } + + pub fn to_bytes_compressed(&self) -> Vec { + self.key.serialize().to_vec() + } + + pub fn compressed(&self) -> bool { + self.compressed + } + + pub fn set_compressed(&mut self, value: bool) { + self.compressed = value; + } + + /// recover message and signature to public key (will be compressed) + pub fn recover_to_pubkey( + msg: &[u8], + sig: &MessageSignature, + ) -> Result { + _secp256k1.with(|ctx| { + let msg = LibSecp256k1Message::from_slice(msg).map_err(|_e| { + "Invalid message: failed to decode data hash: must be a 32-byte hash" + })?; + + let secp256k1_sig = sig + .to_secp256k1_recoverable() + .ok_or("Invalid signature: failed to decode recoverable signature")?; + + let recovered_pubkey = ctx + .recover_ecdsa(&msg, &secp256k1_sig) + .map_err(|_e| "Invalid signature: failed to recover public key")?; + + Ok(Secp256k1PublicKey { + key: recovered_pubkey, + compressed: true, + }) + }) + } + + // for benchmarking + #[cfg(test)] + pub fn recover_benchmark( + msg: &LibSecp256k1Message, + sig: &LibSecp256k1RecoverableSignature, + ) -> Result { + _secp256k1.with(|ctx| { + ctx.recover_ecdsa(msg, sig) + .map_err(|_e| "Invalid signature: failed to recover public key") + }) + } +} + +impl PublicKey for Secp256k1PublicKey { + fn to_bytes(&self) -> Vec { + if self.compressed { + self.key.serialize().to_vec() + } else { + self.key.serialize_uncompressed().to_vec() + } + } + + fn verify(&self, data_hash: &[u8], sig: &MessageSignature) -> Result { + _secp256k1.with(|ctx| { + let msg = LibSecp256k1Message::from_slice(data_hash).map_err(|_e| { + "Invalid message: failed to decode data hash: must be a 32-byte hash" + })?; + + let secp256k1_sig = sig + .to_secp256k1_recoverable() + .ok_or("Invalid signature: failed to decode recoverable signature")?; + + let recovered_pubkey = ctx + .recover_ecdsa(&msg, &secp256k1_sig) + .map_err(|_e| "Invalid signature: failed to recover public key")?; + + if recovered_pubkey != self.key { + test_debug!("{:?} != {:?}", &recovered_pubkey, &self.key); + return Ok(false); + } + + // NOTE: libsecp256k1 _should_ ensure that the S is low, + // but add this check just to be safe. + let secp256k1_sig_standard = secp256k1_sig.to_standard(); + + // must be low-S + let mut secp256k1_sig_low_s = secp256k1_sig_standard; + secp256k1_sig_low_s.normalize_s(); + if secp256k1_sig_low_s != secp256k1_sig_standard { + return Err("Invalid signature: high-S"); + } + + Ok(true) + }) + } +} + +impl Secp256k1PrivateKey { + #[cfg(feature = "rand")] + pub fn random() -> Secp256k1PrivateKey { + use rand::RngCore as _; + + let mut rng = rand::thread_rng(); + loop { + // keep trying to generate valid bytes + let mut random_32_bytes = [0u8; 32]; + rng.fill_bytes(&mut random_32_bytes); + let pk_res = LibSecp256k1PrivateKey::from_slice(&random_32_bytes); + match pk_res { + Ok(pk) => { + return Secp256k1PrivateKey { + key: pk, + compress_public: true, + }; + } + Err(_) => { + continue; + } + } + } + } + + /// Create a Secp256k1PrivateKey from seed bytes by repeatedly + /// SHA256 hashing the seed bytes until a private key is found. + /// + /// If `seed` is a valid private key, it will be returned without hashing. + /// The returned private key's compress_public flag will be `true` + pub fn from_seed(seed: &[u8]) -> Secp256k1PrivateKey { + let mut re_hashed_seed = Vec::from(seed); + loop { + if let Ok(mut sk) = Secp256k1PrivateKey::from_slice(&re_hashed_seed[..]) { + // set this to true: LocalPeer will be doing this anyways, + // and that's currently the only way this method is used + sk.set_compress_public(true); + return sk; + } else { + re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..]) + .as_bytes() + .to_vec() + } + } + } + + pub fn from_hex(hex_string: &str) -> Result { + let data = hex_bytes(hex_string).map_err(|_e| "Failed to decode hex private key")?; + Secp256k1PrivateKey::from_slice(&data[..]).map_err(|_e| "Invalid private key hex string") + } + + pub fn from_slice(data: &[u8]) -> Result { + if data.len() < 32 { + return Err("Invalid private key: shorter than 32 bytes"); + } + if data.len() > 33 { + return Err("Invalid private key: greater than 33 bytes"); + } + let compress_public = if data.len() == 33 { + // compressed byte tag? + if data[32] != 0x01 { + return Err("Invalid private key: invalid compressed byte marker"); + } + true + } else { + false + }; + match LibSecp256k1PrivateKey::from_slice(&data[0..32]) { + Ok(privkey_res) => Ok(Secp256k1PrivateKey { + key: privkey_res, + compress_public, + }), + Err(_e) => Err("Invalid private key: failed to load"), + } + } + + pub fn compress_public(&self) -> bool { + self.compress_public + } + + pub fn set_compress_public(&mut self, value: bool) { + self.compress_public = value; + } + + pub fn to_hex(&self) -> String { + let mut bytes = self.key[..].to_vec(); + if self.compress_public { + bytes.push(1); + } + to_hex(&bytes) + } + + pub fn as_slice(&self) -> &[u8; 32] { + self.key.as_ref() + } +} + +impl PrivateKey for Secp256k1PrivateKey { + fn to_bytes(&self) -> Vec { + let mut bits = self.key[..].to_vec(); + if self.compress_public { + bits.push(0x01); + } + bits + } + + fn sign(&self, data_hash: &[u8]) -> Result { + _secp256k1.with(|ctx| { + let msg = LibSecp256k1Message::from_slice(data_hash).map_err(|_e| { + "Invalid message: failed to decode data hash: must be a 32-byte hash" + })?; + + let sig = ctx.sign_ecdsa_recoverable(&msg, &self.key); + Ok(MessageSignature::from_secp256k1_recoverable(&sig)) + }) + } + + #[cfg(any(test, feature = "testing"))] + fn sign_with_noncedata( + &self, + data_hash: &[u8], + noncedata: &[u8; 32], + ) -> Result { + _secp256k1.with(|ctx| { + let msg = LibSecp256k1Message::from_slice(data_hash).map_err(|_e| { + "Invalid message: failed to decode data hash: must be a 32-byte hash" + })?; + + let sig = ctx.sign_ecdsa_recoverable_with_noncedata(&msg, &self.key, noncedata); + Ok(MessageSignature::from_secp256k1_recoverable(&sig)) + }) + } +} + +fn secp256k1_pubkey_serialize( + pubk: &LibSecp256k1PublicKey, + s: S, +) -> Result { + let key_hex = to_hex(&pubk.serialize()); + s.serialize_str(key_hex.as_str()) +} + +fn secp256k1_pubkey_deserialize<'de, D: serde::Deserializer<'de>>( + d: D, +) -> Result { + let key_hex = String::deserialize(d)?; + let key_bytes = hex_bytes(&key_hex).map_err(de_Error::custom)?; + + LibSecp256k1PublicKey::from_slice(&key_bytes).map_err(de_Error::custom) +} + +fn secp256k1_privkey_serialize( + privk: &LibSecp256k1PrivateKey, + s: S, +) -> Result { + let key_hex = to_hex(&privk[..]); + s.serialize_str(key_hex.as_str()) +} + +fn secp256k1_privkey_deserialize<'de, D: serde::Deserializer<'de>>( + d: D, +) -> Result { + let key_hex = String::deserialize(d)?; + let key_bytes = hex_bytes(&key_hex).map_err(de_Error::custom)?; + + LibSecp256k1PrivateKey::from_slice(&key_bytes[..]).map_err(de_Error::custom) +} + +pub fn secp256k1_recover( + message_arr: &[u8], + serialized_signature_arr: &[u8], +) -> Result<[u8; 33], LibSecp256k1Error> { + _secp256k1.with(|ctx| { + let message = LibSecp256k1Message::from_slice(message_arr)?; + + let rec_id = LibSecp256k1RecoveryID::from_i32(serialized_signature_arr[64] as i32)?; + let recovered_sig = LibSecp256k1RecoverableSignature::from_compact( + &serialized_signature_arr[..64], + rec_id, + )?; + let recovered_pub = ctx.recover_ecdsa(&message, &recovered_sig)?; + let recovered_serialized = recovered_pub.serialize(); // 33 bytes version + + Ok(recovered_serialized) + }) +} + +pub fn secp256k1_verify( + message_arr: &[u8], + serialized_signature_arr: &[u8], + pubkey_arr: &[u8], +) -> Result<(), LibSecp256k1Error> { + _secp256k1.with(|ctx| { + let message = LibSecp256k1Message::from_slice(message_arr)?; + let expanded_sig = LibSecp256k1Signature::from_compact(&serialized_signature_arr[..64])?; // ignore 65th byte if present + let pubkey = LibSecp256k1PublicKey::from_slice(pubkey_arr)?; + ctx.verify_ecdsa(&message, &expanded_sig, &pubkey) + }) +} + +#[cfg(test)] +mod tests { + use rand::RngCore as _; + use secp256k1; + use secp256k1::{PublicKey as LibSecp256k1PublicKey, Secp256k1}; + + use super::*; + use crate::util::get_epoch_time_ms; + use crate::util::hash::hex_bytes; + + struct KeyFixture { + input: I, + result: R, + } + + #[derive(Debug)] + struct VerifyFixture { + public_key: &'static str, + data: &'static str, + signature: &'static str, + result: R, + } + + #[test] + fn test_parse_serialize_compressed() { + let mut t1 = Secp256k1PrivateKey::random(); + t1.set_compress_public(true); + let h_comp = t1.to_hex(); + t1.set_compress_public(false); + let h_uncomp = t1.to_hex(); + + assert!(h_comp != h_uncomp); + assert_eq!(h_comp.len(), 66); + assert_eq!(h_uncomp.len(), 64); + + let (uncomp, comp_value) = h_comp.split_at(64); + assert_eq!(comp_value, "01"); + assert_eq!(uncomp, &h_uncomp); + + assert!(Secp256k1PrivateKey::from_hex(&h_comp) + .unwrap() + .compress_public()); + assert!(!Secp256k1PrivateKey::from_hex(&h_uncomp) + .unwrap() + .compress_public()); + + assert_eq!(Secp256k1PrivateKey::from_hex(&h_uncomp), Ok(t1.clone())); + + t1.set_compress_public(true); + + assert_eq!(Secp256k1PrivateKey::from_hex(&h_comp), Ok(t1)); + } + + #[test] + /// Test the behavior of from_seed using hard-coded values from previous existing integration tests + fn sk_from_seed() { + let sk = Secp256k1PrivateKey::from_seed(&[2; 32]); + assert_eq!( + Secp256k1PublicKey::from_private(&sk).to_hex(), + "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766" + ); + assert_eq!( + sk.to_hex(), + "020202020202020202020202020202020202020202020202020202020202020201" + ); + + let sk = Secp256k1PrivateKey::from_seed(&[0]); + assert_eq!( + Secp256k1PublicKey::from_private(&sk).to_hex(), + "0243311589af63c2adda04fcd7792c038a05c12a4fe40351b3eb1612ff6b2e5a0e" + ); + assert_eq!( + sk.to_hex(), + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d01" + ); + } + + #[test] + fn test_parse_serialize() { + let ctx: Secp256k1 = Secp256k1::new(); + let fixtures = vec![ + KeyFixture { + input: "0233d78f74de8ef4a1de815b6d5c5c129c073786305c0826c499b1811c9a12cee5", + result: Some(Secp256k1PublicKey { + key: LibSecp256k1PublicKey::from_slice(&hex_bytes("0233d78f74de8ef4a1de815b6d5c5c129c073786305c0826c499b1811c9a12cee5").unwrap()[..]).unwrap(), + compressed: true + }) + }, + KeyFixture { + input: "044a83ad59dbae1e2335f488dbba5f8604d00f612a43ebaae784b5b7124cc38c3aaf509362787e1a8e25131724d57fec81b87889aabb4edf7bd89f5c4daa4f8aa7", + result: Some(Secp256k1PublicKey { + key: LibSecp256k1PublicKey::from_slice(&hex_bytes("044a83ad59dbae1e2335f488dbba5f8604d00f612a43ebaae784b5b7124cc38c3aaf509362787e1a8e25131724d57fec81b87889aabb4edf7bd89f5c4daa4f8aa7").unwrap()[..]).unwrap(), + compressed: false + }) + }, + KeyFixture { + input: "0233d78f74de8ef4a1de815b6d5c5c129c073786305c0826c499b1811c9a12ce", + result: None, + }, + KeyFixture { + input: "044a83ad59dbae1e2335f488dbba5f8604d00f612a43ebaae784b5b7124cc38c3aaf509362787e1a8e25131724d57fec81b87889aabb4edf7bd89f5c4daa4f8a", + result: None, + } + ]; + + for fixture in fixtures { + let key_res = Secp256k1PublicKey::from_hex(fixture.input); + match (key_res, fixture.result) { + (Ok(key), Some(key_result)) => { + assert_eq!(key, key_result); + + let key_from_slice = + Secp256k1PublicKey::from_slice(&hex_bytes(fixture.input).unwrap()[..]) + .unwrap(); + assert_eq!(key_from_slice, key_result); + + let key_bytes = key.to_bytes(); + assert_eq!(key_bytes, hex_bytes(fixture.input).unwrap()); + } + (Err(_e), None) => {} + (_, _) => { + // either got a key when we didn't expect one, or didn't get a key when we did + // expect one. + panic!("Unexpected result: we either got a key when we didn't expect one, or didn't get a key when we did expect one."); + } + } + } + } + + #[test] + fn test_verify() { + let _ctx: Secp256k1 = Secp256k1::new(); + let fixtures : Vec>> = vec![ + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Ok(true) + }, + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", + data: "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce", // sha256 hash of "nope" + result: Ok(false) + }, + VerifyFixture { + public_key: "034c35b09b758678165d6ed84a50b329900c99986cf8e9a358ceae0d03af91f5b6", // wrong key + signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Ok(false) + }, + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "00354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe7", // wrong sig (bad s) + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Ok(false) + }, + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "00454445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad r) + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Ok(false) + }, + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "01354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad recovery) + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Ok(false) + }, + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "02354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad recovery) + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Err("Invalid signature: failed to recover public key"), + }, + VerifyFixture { + public_key: "0385f2e2867524289d6047d0d9c5e764c5d413729fc32291ad2c353fbc396a4219", + signature: "03354445a1dc98a1bd27984dbe69979a5cd77886b4d9134af5c40e634d96e1cb445b97de5b632582d31704f86706a780886e6e381bfed65228267358262d203fe6", // wrong sig (bad recovery) + data: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", // sha256 hash of "hello world" + result: Err("Invalid signature: failed to recover public key"), + } + ]; + + for fixture in fixtures { + let key = Secp256k1PublicKey::from_hex(fixture.public_key).unwrap(); + let signature = MessageSignature::from_raw(&hex_bytes(fixture.signature).unwrap()); + let ver_res = key.verify(&hex_bytes(fixture.data).unwrap(), &signature); + match (ver_res, fixture.result) { + (Ok(true), Ok(true)) => {} + (Ok(false), Ok(false)) => {} + (Err(e1), Err(e2)) => assert_eq!(e1, e2), + (Err(e1), _) => { + test_debug!("Failed to verify signature: {}", e1); + panic!( + "failed fixture (verification: {:?}): {:#?}", + &ver_res, &fixture + ); + } + (_, _) => { + panic!( + "failed fixture (verification: {:?}): {:#?}", + &ver_res, &fixture + ); + } + } + } + } + + #[test] + #[ignore] + fn test_verify_benchmark_roundtrip() { + let mut runtime_sign = 0; + let mut runtime_verify = 0; + let mut runtime_recover = 0; + let mut rng = rand::thread_rng(); + + for i in 0..100 { + let privk = Secp256k1PrivateKey::random(); + let pubk = Secp256k1PublicKey::from_private(&privk); + + let mut msg = [0u8; 32]; + rng.fill_bytes(&mut msg); + + let sign_start = get_epoch_time_ms(); + for i in 0..1000 { + let sig = privk.sign(&msg).unwrap(); + } + let sign_end = get_epoch_time_ms(); + + let sig = privk.sign(&msg).unwrap(); + let secp256k1_msg = LibSecp256k1Message::from_slice(&msg).unwrap(); + let secp256k1_sig = sig.to_secp256k1_recoverable().unwrap(); + + let recovered_pubk = + Secp256k1PublicKey::recover_benchmark(&secp256k1_msg, &secp256k1_sig).unwrap(); + assert_eq!(recovered_pubk, pubk.key); + + let recover_start = get_epoch_time_ms(); + for i in 0..1000 { + let recovered_pubk = + Secp256k1PublicKey::recover_benchmark(&secp256k1_msg, &secp256k1_sig).unwrap(); + } + let recover_end = get_epoch_time_ms(); + + let verify_start = get_epoch_time_ms(); + for i in 0..1000 { + let valid = pubk.verify(&msg, &sig).unwrap(); + } + let verify_end = get_epoch_time_ms(); + + let valid = pubk.verify(&msg, &sig).unwrap(); + assert!(valid); + + test_debug!( + "Runtime: {:?} sign, {:?} recover, {:?} verify", + ((sign_end - sign_start) as f64) / 1000.0, + ((recover_end - recover_start) as f64) / 1000.0, + ((verify_end - verify_start) as f64) / 1000.0 + ); + + runtime_sign += sign_end - sign_start; + runtime_verify += verify_end - verify_start; + runtime_recover += recover_end - recover_start; + } + + test_debug!( + "Total Runtime: {:?} sign, {:?} verify, {:?} recover, {:?} verify - recover", + runtime_sign, + runtime_verify, + runtime_recover, + runtime_verify - runtime_recover + ); + } +} diff --git a/stacks-common/src/util/secp256k1/wasm.rs b/stacks-common/src/util/secp256k1/wasm.rs new file mode 100644 index 00000000000..55b5266bfd5 --- /dev/null +++ b/stacks-common/src/util/secp256k1/wasm.rs @@ -0,0 +1,383 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use ::libsecp256k1; +use ::libsecp256k1::curve::Scalar; +pub use ::libsecp256k1::Error; +#[cfg(not(feature = "wasm-deterministic"))] +use ::libsecp256k1::{Error as LibSecp256k1Error, Message as LibSecp256k1Message}; +use ::libsecp256k1::{ + PublicKey as LibSecp256k1PublicKey, RecoveryId as LibSecp256k1RecoveryId, + SecretKey as LibSecp256k1PrivateKey, Signature as LibSecp256k1Signature, ECMULT_GEN_CONTEXT, +}; +use serde::de::{Deserialize, Error as de_Error}; +use serde::Serialize; + +use super::MessageSignature; +use crate::types::{PrivateKey, PublicKey}; +use crate::util::hash::{hex_bytes, to_hex}; + +pub const PUBLIC_KEY_SIZE: usize = 33; + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Secp256k1PublicKey { + // serde is broken for secp256k1, so do it ourselves + #[serde( + serialize_with = "secp256k1_pubkey_serialize", + deserialize_with = "secp256k1_pubkey_deserialize" + )] + key: LibSecp256k1PublicKey, + compressed: bool, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct Secp256k1PrivateKey { + // serde is broken for secp256k1, so do it ourselves + #[serde( + serialize_with = "secp256k1_privkey_serialize", + deserialize_with = "secp256k1_privkey_deserialize" + )] + key: LibSecp256k1PrivateKey, + compress_public: bool, +} + +impl Secp256k1PublicKey { + pub fn from_slice(data: &[u8]) -> Result { + let (format, compressed) = if data.len() == PUBLIC_KEY_SIZE { + (libsecp256k1::PublicKeyFormat::Compressed, true) + } else { + (libsecp256k1::PublicKeyFormat::Full, false) + }; + match LibSecp256k1PublicKey::parse_slice(data, Some(format)) { + Ok(pubkey_res) => Ok(Secp256k1PublicKey { + key: pubkey_res, + compressed, + }), + Err(_e) => Err("Invalid public key: failed to load"), + } + } + + pub fn to_hex(&self) -> String { + if self.compressed { + to_hex(&self.key.serialize_compressed().to_vec()) + } else { + to_hex(&self.key.serialize().to_vec()) + } + } + + pub fn to_bytes_compressed(&self) -> Vec { + self.key.serialize_compressed().to_vec() + } + + pub fn compressed(&self) -> bool { + self.compressed + } + + pub fn set_compressed(&mut self, value: bool) { + self.compressed = value; + } + + pub fn to_bytes(&self) -> Vec { + if self.compressed { + self.key.serialize_compressed().to_vec() + } else { + self.key.serialize().to_vec() + } + } + + pub fn from_hex(hex_string: &str) -> Result { + let data = hex_bytes(hex_string).map_err(|_e| "Failed to decode hex public key")?; + Secp256k1PublicKey::from_slice(&data[..]).map_err(|_e| "Invalid public key hex string") + } + + #[cfg(not(feature = "wasm-deterministic"))] + pub fn from_private(privk: &Secp256k1PrivateKey) -> Secp256k1PublicKey { + let key = + LibSecp256k1PublicKey::from_secret_key_with_context(&privk.key, &ECMULT_GEN_CONTEXT); + Secp256k1PublicKey { + key, + compressed: privk.compress_public, + } + } + + #[cfg(not(feature = "wasm-deterministic"))] + /// recover message and signature to public key (will be compressed) + pub fn recover_to_pubkey( + msg: &[u8], + sig: &MessageSignature, + ) -> Result { + let secp256k1_sig = secp256k1_recover(msg, sig.as_bytes()) + .map_err(|_e| "Invalid signature: failed to recover public key")?; + + Secp256k1PublicKey::from_slice(&secp256k1_sig) + } +} + +impl Secp256k1PrivateKey { + #[cfg(feature = "rand")] + pub fn new() -> Secp256k1PrivateKey { + use rand::RngCore as _; + + let mut rng = rand::thread_rng(); + loop { + // keep trying to generate valid bytes + let mut random_32_bytes = [0u8; 32]; + rng.fill_bytes(&mut random_32_bytes); + let pk_res = LibSecp256k1PrivateKey::parse_slice(&random_32_bytes); + match pk_res { + Ok(pk) => { + return Secp256k1PrivateKey { + key: pk, + compress_public: true, + }; + } + Err(_) => { + continue; + } + } + } + } + + pub fn from_slice(data: &[u8]) -> Result { + if data.len() < 32 { + return Err("Invalid private key: shorter than 32 bytes"); + } + if data.len() > 33 { + return Err("Invalid private key: greater than 33 bytes"); + } + let compress_public = if data.len() == 33 { + // compressed byte tag? + if data[32] != 0x01 { + return Err("Invalid private key: invalid compressed byte marker"); + } + true + } else { + false + }; + + match LibSecp256k1PrivateKey::parse_slice(&data[0..32]) { + Ok(privkey_res) => Ok(Secp256k1PrivateKey { + key: privkey_res, + compress_public, + }), + Err(_e) => Err("Invalid private key: failed to load"), + } + } + + pub fn from_hex(hex_string: &str) -> Result { + let data = hex_bytes(hex_string).map_err(|_e| "Failed to decode hex private key")?; + Secp256k1PrivateKey::from_slice(&data[..]).map_err(|_e| "Invalid private key hex string") + } + + pub fn compress_public(&self) -> bool { + self.compress_public + } + + pub fn set_compress_public(&mut self, value: bool) { + self.compress_public = value; + } +} + +#[cfg(not(feature = "wasm-deterministic"))] +pub fn secp256k1_recover( + message_arr: &[u8], + serialized_signature: &[u8], +) -> Result<[u8; 33], LibSecp256k1Error> { + let recovery_id = libsecp256k1::RecoveryId::parse(serialized_signature[64] as u8)?; + let message = LibSecp256k1Message::parse_slice(message_arr)?; + let signature = LibSecp256k1Signature::parse_standard_slice(&serialized_signature[..64])?; + let recovered_pub_key = libsecp256k1::recover(&message, &signature, &recovery_id)?; + Ok(recovered_pub_key.serialize_compressed()) +} + +#[cfg(not(feature = "wasm-deterministic"))] +pub fn secp256k1_verify( + message_arr: &[u8], + serialized_signature: &[u8], + pubkey_arr: &[u8], +) -> Result<(), LibSecp256k1Error> { + let message = LibSecp256k1Message::parse_slice(message_arr)?; + let signature = LibSecp256k1Signature::parse_standard_slice(&serialized_signature[..64])?; // ignore 65th byte if present + let pubkey = LibSecp256k1PublicKey::parse_slice( + pubkey_arr, + Some(libsecp256k1::PublicKeyFormat::Compressed), + )?; + + let res = libsecp256k1::verify(&message, &signature, &pubkey); + if res { + Ok(()) + } else { + Err(LibSecp256k1Error::InvalidPublicKey) + } +} + +fn secp256k1_pubkey_serialize( + pubk: &LibSecp256k1PublicKey, + s: S, +) -> Result { + let key_hex = to_hex(&pubk.serialize().to_vec()); + s.serialize_str(&key_hex.as_str()) +} + +fn secp256k1_pubkey_deserialize<'de, D: serde::Deserializer<'de>>( + d: D, +) -> Result { + let key_hex = String::deserialize(d)?; + let key_bytes = hex_bytes(&key_hex).map_err(de_Error::custom)?; + + LibSecp256k1PublicKey::parse_slice(&key_bytes[..], None).map_err(de_Error::custom) +} + +fn secp256k1_privkey_serialize( + privk: &LibSecp256k1PrivateKey, + s: S, +) -> Result { + let key_hex = to_hex(&privk.serialize().to_vec()); + s.serialize_str(key_hex.as_str()) +} + +fn secp256k1_privkey_deserialize<'de, D: serde::Deserializer<'de>>( + d: D, +) -> Result { + let key_hex = String::deserialize(d)?; + let key_bytes = hex_bytes(&key_hex).map_err(de_Error::custom)?; + + LibSecp256k1PrivateKey::parse_slice(&key_bytes[..]).map_err(de_Error::custom) +} + +impl MessageSignature { + pub fn empty() -> MessageSignature { + // NOTE: this cannot be a valid signature + MessageSignature([0u8; 65]) + } + + #[cfg(test)] + // test method for generating place-holder data + pub fn from_raw(sig: &Vec) -> MessageSignature { + let mut buf = [0u8; 65]; + if sig.len() < 65 { + buf.copy_from_slice(&sig[..]); + } else { + buf.copy_from_slice(&sig[..65]); + } + MessageSignature(buf) + } + + pub fn from_secp256k1_recoverable( + sig: &LibSecp256k1Signature, + recid: LibSecp256k1RecoveryId, + ) -> MessageSignature { + let bytes = sig.serialize(); + let mut ret_bytes = [0u8; 65]; + let recovery_id_byte = recid.serialize(); // recovery ID will be 0, 1, 2, or 3 + ret_bytes[0] = recovery_id_byte; + ret_bytes[1..=64].copy_from_slice(&bytes[..64]); + MessageSignature(ret_bytes) + } + + pub fn to_secp256k1_recoverable( + &self, + ) -> Option<(LibSecp256k1Signature, LibSecp256k1RecoveryId)> { + let recovery_id = match LibSecp256k1RecoveryId::parse(self.0[0]) { + Ok(rid) => rid, + Err(_) => { + return None; + } + }; + let signature = LibSecp256k1Signature::parse_standard_slice(&self.0[1..65]).ok()?; + Some((signature, recovery_id)) + } +} + +impl PublicKey for Secp256k1PublicKey { + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + #[cfg(feature = "wasm-deterministic")] + fn verify(&self, _data_hash: &[u8], _sig: &MessageSignature) -> Result { + Err("Not implemented for wasm-deterministic") + } + + #[cfg(not(feature = "wasm-deterministic"))] + fn verify(&self, data_hash: &[u8], sig: &MessageSignature) -> Result { + let pub_key = Secp256k1PublicKey::recover_to_pubkey(data_hash, sig)?; + Ok(self.eq(&pub_key)) + } +} + +impl PrivateKey for Secp256k1PrivateKey { + fn to_bytes(&self) -> Vec { + let mut bits = self.key.serialize().to_vec(); + if self.compress_public { + bits.push(0x01); + } + bits + } + + #[cfg(feature = "wasm-deterministic")] + fn sign(&self, _data_hash: &[u8]) -> Result { + Err("Not implemented for wasm-deterministic") + } + + #[cfg(not(feature = "wasm-deterministic"))] + fn sign(&self, data_hash: &[u8]) -> Result { + let message = LibSecp256k1Message::parse_slice(data_hash) + .map_err(|_e| "Invalid message: failed to decode data hash: must be a 32-byte hash")?; + let (sig, recid) = libsecp256k1::sign(&message, &self.key); + let rec_sig = MessageSignature::from_secp256k1_recoverable(&sig, recid); + Ok(rec_sig) + } + + #[cfg(all(feature = "wasm-deterministic", any(test, feature = "testing")))] + fn sign_with_noncedata( + &self, + data_hash: &[u8], + noncedata: &[u8; 32], + ) -> Result { + Err("Not implemented for wasm-deterministic") + } + + #[cfg(all(any(test, feature = "testing"), not(feature = "wasm-deterministic")))] + fn sign_with_noncedata( + &self, + data_hash: &[u8], + noncedata: &[u8; 32], + ) -> Result { + let message = LibSecp256k1Message::parse_slice(data_hash) + .map_err(|_e| "Invalid message: failed to decode data hash: must be a 32-byte hash")?; + let mut nonce = Scalar::default(); + let _ = nonce.set_b32(&noncedata); + + // we need this as the key raw data are private + let mut key = Scalar::default(); + let _ = key.set_b32(&self.key.serialize()); + + let (sigr, sigs, recid) = match ECMULT_GEN_CONTEXT.sign_raw(&key, &message.0, &nonce) { + Ok(result) => result, + Err(_) => return Err("unable to sign message"), + }; + + let recid = match LibSecp256k1RecoveryId::parse(recid) { + Ok(recid) => recid, + Err(_) => return Err("invalid recovery id"), + }; + + let (sig, recid) = (LibSecp256k1Signature { r: sigr, s: sigs }, recid); + let rec_sig = MessageSignature::from_secp256k1_recoverable(&sig, recid); + Ok(rec_sig) + } +} diff --git a/stacks-signer/src/signerdb.rs b/stacks-signer/src/signerdb.rs index e02f46afd1c..b0e0cc03bd1 100644 --- a/stacks-signer/src/signerdb.rs +++ b/stacks-signer/src/signerdb.rs @@ -1995,6 +1995,7 @@ pub mod tests { TransactionVersion, }; use clarity::types::chainstate::{StacksBlockId, StacksPrivateKey, StacksPublicKey}; + use clarity::types::PrivateKey; use clarity::util::hash::Hash160; use clarity::util::secp256k1::MessageSignature; use libsigner::v0::messages::{StateMachineUpdateContent, StateMachineUpdateMinerState}; @@ -2467,10 +2468,25 @@ pub mod tests { let address1 = StacksAddress::p2pkh(false, &public_key1); let address2 = StacksAddress::p2pkh(false, &public_key2); - let signature1 = MessageSignature::from_raw(&[0x11]); - let signature2 = MessageSignature::from_raw(&[0x22]); - let signature3 = MessageSignature::from_raw(&[0x33]); - let signature4 = MessageSignature::from_raw(&[0x44]); + let nonce1 = [0x11u8; 32]; + let signature1 = private_key1 + .sign_with_noncedata(&block_id.0, &nonce1) + .unwrap(); + + let nonce2 = [0x22u8; 32]; + let signature2 = private_key1 + .sign_with_noncedata(&block_id.0, &nonce2) + .unwrap(); + + let nonce3 = [0x33u8; 32]; + let signature3 = private_key1 + .sign_with_noncedata(&block_id.0, &nonce3) + .unwrap(); + + let nonce4 = [0x44u8; 32]; + let signature4 = private_key2 + .sign_with_noncedata(&block_id.0, &nonce4) + .unwrap(); assert_eq!(db.get_block_signatures(&block_id).unwrap(), vec![]); diff --git a/stackslib/fuzz/Cargo.lock b/stackslib/fuzz/Cargo.lock index e17fd2f7514..a65535f3c51 100644 --- a/stackslib/fuzz/Cargo.lock +++ b/stackslib/fuzz/Cargo.lock @@ -44,6 +44,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "autocfg" version = "1.5.0" @@ -56,6 +62,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.8.0" @@ -74,6 +86,15 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -178,6 +199,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -200,6 +227,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -209,7 +246,7 @@ dependencies = [ "cfg-if 1.0.3", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "serde", @@ -246,13 +283,22 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -276,7 +322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "serdect", @@ -301,7 +347,7 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", - "sha2", + "sha2 0.10.9", "subtle", ] @@ -313,7 +359,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -477,13 +523,34 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", ] [[package]] @@ -672,20 +739,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if 1.0.3", - "ecdsa", - "elliptic-curve", - "once_cell", - "serdect", - "sha2", -] - [[package]] name = "keccak" version = "0.1.5" @@ -727,6 +780,55 @@ dependencies = [ "cc", ] +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "lazy_static", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libsqlite3-sys" version = "0.28.0" @@ -744,7 +846,7 @@ version = "0.0.1" dependencies = [ "clarity", "serde", - "sha2", + "sha2 0.10.9", "stacks-common", ] @@ -851,6 +953,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "p256" version = "0.13.2" @@ -861,7 +969,7 @@ dependencies = [ "elliptic-curve", "primeorder", "serdect", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1027,7 +1135,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] @@ -1037,7 +1145,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1091,6 +1199,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1161,6 +1288,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.3", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1169,7 +1309,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if 1.0.3", "cpufeatures", - "digest", + "digest 0.10.7", "sha2-asm", ] @@ -1188,7 +1328,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -1204,7 +1344,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] @@ -1283,17 +1423,18 @@ dependencies = [ "curve25519-dalek", "ed25519-dalek", "hashbrown 0.15.5", - "k256", "lazy_static", + "libsecp256k1", "nix", "p256", "rand", "ripemd", "rusqlite", + "secp256k1", "serde", "serde_derive", "serde_json", - "sha2", + "sha2 0.10.9", "sha3", "slog", "slog-term", @@ -1323,7 +1464,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha2", + "sha2 0.10.9", "siphasher", "slog", "stacks-common",