mirror of
https://github.com/pezkuwichain/pezkuwi-wasm.git
synced 2026-04-21 23:48:00 +00:00
151 lines
5.4 KiB
Rust
151 lines
5.4 KiB
Rust
// 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<u8> {
|
|
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<u8> {
|
|
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<u8> {
|
|
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<u8> {
|
|
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<u8> {
|
|
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);
|
|
}
|
|
}
|