diff --git a/consensus/wasm/src/keypair.rs b/consensus/wasm/src/keypair.rs index 9779ec2b5..e4bbebcd4 100644 --- a/consensus/wasm/src/keypair.rs +++ b/consensus/wasm/src/keypair.rs @@ -246,3 +246,9 @@ impl TryFrom for PublicKey { } } } + +impl From for XOnlyPublicKey { + fn from(value: PublicKey) -> Self { + value.xonly_public_key + } +} diff --git a/wallet/core/src/message.rs b/wallet/core/src/message.rs index 9bdb96b42..ab74d8b4b 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -20,17 +20,6 @@ pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32]) -> Result Ok(sig.to_vec()) } -pub fn sign_message_with_aux_rand(msg: &PersonalMessage, privkey: &[u8; 32], aux_rand: &[u8; 32]) -> Result, Error> { - let hash = calc_personal_message_hash(msg); - - let msg = secp256k1::Message::from_slice(hash.as_bytes().as_slice())?; - let schnorr_key = secp256k1::KeyPair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; - let curve = secp256k1::Secp256k1::new(); - let sig: [u8; 64] = *curve.sign_schnorr_with_aux_rand(&msg, &schnorr_key, aux_rand).as_ref(); - - Ok(sig.to_vec()) -} - /// Ok(()) if the signature matches the given message and pubkey /// Error if any of the inputs are incorrect, or the signature is invalid pub fn verify_message(msg: &PersonalMessage, signature: &Vec, pubkey: &XOnlyPublicKey) -> Result<(), Error> { @@ -50,6 +39,19 @@ fn calc_personal_message_hash(msg: &PersonalMessage) -> Hash { mod tests { use super::*; + /// Sign message equivalent that's only used for tests + /// Necessary only because of KIP test vectors + fn sign_message_with_aux_rand(msg: &PersonalMessage, privkey: &[u8; 32], aux_rand: &[u8; 32]) -> Result, Error> { + let hash = calc_personal_message_hash(msg); + + let msg = secp256k1::Message::from_slice(hash.as_bytes().as_slice())?; + let schnorr_key = secp256k1::KeyPair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; + let curve = secp256k1::Secp256k1::new(); + let sig: [u8; 64] = *curve.sign_schnorr_with_aux_rand(&msg, &schnorr_key, aux_rand).as_ref(); + + Ok(sig.to_vec()) + } + #[test] fn test_basic_sign_and_verify_sign() { let pm = PersonalMessage("Hello Kaspa!"); diff --git a/wallet/core/src/wasm/message.rs b/wallet/core/src/wasm/message.rs new file mode 100644 index 000000000..bf232f025 --- /dev/null +++ b/wallet/core/src/wasm/message.rs @@ -0,0 +1,45 @@ +use crate::imports::*; +use crate::message::*; +use kaspa_consensus_wasm::{PrivateKey, PublicKey}; + +/// Signs a message with the given private key +/// @param {object} value - an object containing { message: String, privateKey: String|PrivateKey } +/// @returns {String} the signature, in hex string format +#[wasm_bindgen(js_name = signMessage, skip_jsdoc)] +pub fn js_sign_message(value: JsValue) -> Result { + if let Some(object) = Object::try_from(&value) { + let private_key = object.get::("privateKey")?; + let raw_msg = object.get_string("message")?; + let mut privkey_bytes = [0u8; 32]; + + privkey_bytes.copy_from_slice(&private_key.secret_bytes()); + + let pm = PersonalMessage(&raw_msg); + + let sig_vec = sign_message(&pm, &privkey_bytes)?; + + Ok(faster_hex::hex_string(sig_vec.as_slice())) + } else { + Err(Error::custom("Failed to parse input")) + } +} + +/// Verifies with a public key the signature of the given message +/// @param {object} value - an object containing { message: String, signature: String, publicKey: String|PublicKey } +/// @returns {bool} true if the signature can be verified with the given public key and message, false otherwise +#[wasm_bindgen(js_name = verifyMessage, skip_jsdoc)] +pub fn js_verify_message(value: JsValue) -> Result { + if let Some(object) = Object::try_from(&value) { + let public_key = object.get::("publicKey")?; + let raw_msg = object.get_string("message")?; + let signature = object.get_string("signature")?; + + let pm = PersonalMessage(&raw_msg); + let mut signature_bytes = [0u8; 64]; + faster_hex::hex_decode(signature.as_bytes(), &mut signature_bytes)?; + + Ok(verify_message(&pm, &signature_bytes.to_vec(), &public_key.into()).is_ok()) + } else { + Err(Error::custom("Failed to parse input")) + } +} diff --git a/wallet/core/src/wasm/mod.rs b/wallet/core/src/wasm/mod.rs index d2a9ad82e..bae0e3bc0 100644 --- a/wallet/core/src/wasm/mod.rs +++ b/wallet/core/src/wasm/mod.rs @@ -1,4 +1,5 @@ pub mod balance; +pub mod message; pub mod tx; pub mod utils; pub mod utxo; @@ -7,6 +8,7 @@ pub mod xprivatekey; pub mod xpublickey; pub use balance::*; +pub use message::*; pub use tx::*; pub use utils::*; pub use utxo::*; diff --git a/wasm/nodejs/message_signing.js b/wasm/nodejs/message_signing.js new file mode 100644 index 000000000..d2bd0921e --- /dev/null +++ b/wasm/nodejs/message_signing.js @@ -0,0 +1,30 @@ +let kaspa = require('./kaspa/kaspa_wasm'); +let { + PrivateKey, + PublicKey, + signMessage, + verifyMessage, +} = kaspa; + +kaspa.initConsolePanicHook(); + +let message = 'Hello Kaspa!'; +let privkey = 'B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'; +let pubkey = 'DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659'; + +function runDemo(message, privateKey, publicKey) { + let signature = signMessage({message, privateKey}); + + console.info(`Message: ${message} => Signature: ${signature}`); + + if (verifyMessage({message, signature, publicKey})) { + console.info('Signature verified!'); + } else { + console.info('Signature is invalid!'); + } +} + +// Using strings: +runDemo(message, privkey, pubkey); +// Using Objects: +runDemo(message, new PrivateKey(privkey), new PublicKey(pubkey));