// Copyright 2019-2025 @pezkuwi/wasm-crypto authors & contributors // SPDX-License-Identifier: Apache-2.0 use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, Message, PublicKey, SecretKey, SECP256K1, }; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn ext_secp_pub_compress(pubkey: &[u8]) -> Vec { match PublicKey::from_slice(&pubkey) { Ok(p) => p.serialize().to_vec(), _ => panic!("Invalid pubkey provided."), } } #[wasm_bindgen] pub fn ext_secp_pub_expand(pubkey: &[u8]) -> Vec { match PublicKey::from_slice(&pubkey) { Ok(p) => p.serialize_uncompressed().to_vec(), _ => panic!("Invalid pubkey provided."), } } #[wasm_bindgen] pub fn ext_secp_from_seed(seed: &[u8]) -> Vec { match SecretKey::from_slice(seed) { Ok(s) => { let mut res = vec![]; let pubkey = PublicKey::from_secret_key(SECP256K1, &s); res.extend_from_slice(&s.serialize_secret()); res.extend_from_slice(&pubkey.serialize()); res } _ => panic!("Invalid seed provided."), } } #[wasm_bindgen] pub fn ext_secp_recover(hash: &[u8], sig: &[u8], rec: i32) -> Vec { match RecoveryId::from_i32(rec) { Ok(r) => match ( Message::from_slice(hash), RecoverableSignature::from_compact(&sig, r), ) { (Ok(m), Ok(s)) => { let standard = s.to_standard(); let mut normalized = standard; normalized.normalize_s(); // check if the signature is normalized if normalized.ne(&standard) { panic!("Non-normalized signature provided."); } match s.recover(&m) { Ok(k) => k.serialize().to_vec(), _ => panic!("Unable to recover."), } } _ => panic!("Invalid signature provided."), }, _ => panic!("Invalid recovery data provided."), } } #[wasm_bindgen] pub fn ext_secp_sign(hash: &[u8], seckey: &[u8]) -> Vec { match (Message::from_slice(hash), SecretKey::from_slice(seckey)) { (Ok(m), Ok(s)) => { let mut res = vec![]; let (rec, sig) = SECP256K1.sign_ecdsa_recoverable(&m, &s).serialize_compact(); res.extend_from_slice(&sig); res.push(rec.to_i32() as u8); res } _ => panic!("Invalid message or secret provided."), } } #[cfg(test)] pub mod tests { use super::*; use hex_literal::hex; #[test] fn can_create_pair() { let seckey = hex!("4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae"); let expected = hex!("4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae03fd8c74f795ced92064b86191cb2772b1e3a0947740aa0a5a6e379592471fd85b"); let res = ext_secp_from_seed(&seckey); assert_eq!(res[..], expected[..]); } #[test] fn can_pub_compress_full() { let pubkey = hex!("04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af"); let expected = hex!("03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077"); let res = ext_secp_pub_compress(&pubkey); assert_eq!(res[..], expected[..]); } #[test] fn can_pub_expand_comp() { let pubkey = hex!("03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077"); let expected = hex!("04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af"); let res = ext_secp_pub_expand(&pubkey); assert_eq!(res[..], expected[..]); } #[test] fn can_recover() { let expected = hex!("028d13da15a02f3a70677339d51b14177ee9b49657662b35e56a9d9dee17db1d30"); let sig = hex!("7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff"); let msg = hex!("a30b64ce1eedf409c8afb801d72c05234e64849ea538c15dd3c8cf4ffcf166c9"); let res = ext_secp_recover(&msg, &sig, 0); assert_eq!(res[..], expected[..]); } #[test] fn can_sign() { // JS expectation - doesn't match? // let expected = hex!("df92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba8c4efa521361678d9b8582744d77aa4b8d886d7380b7808a683174afad9c4700300"); let expected = hex!("df92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba83b105adec9e9872647a7d8bb28855b45e22805aea3d097953cbb1391f671d13e01"); let seckey = hex!("4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae"); let msg = hex!("68c731589a583d08b70861683b59ce3dd56284cb2f0da5b6cd83e6641dac3aab"); let res = ext_secp_sign(&msg, &seckey); assert_eq!(res[..], expected[..]); } #[test] #[should_panic(expected = "Non-normalized signature provided.")] fn rejects_high_s_signature() { // valid ECDSA signature with a high S value (non-normalized) let sig = hex!( "fa1b7a71500322d70bcd5b982c678c96c95f68893063e1621b8b85c867b80b1caae8c7c4f8b5c6d181e4c8898a3f4c329da0e2c21f6f80552a660e6c1d9fa201"); let msg = hex!("9a3c9be3a14a94df8cda1c474c0e9f8b1b35b1d85d2122cb87e6eb3c518e3be3"); let _ = ext_secp_recover(&msg, &sig, 0); } }