From 16043140f8c44c99c3d03a287ecd94512f7cc732 Mon Sep 17 00:00:00 2001 From: Witter Date: Sun, 3 Nov 2024 23:58:32 +0800 Subject: [PATCH 1/5] feat: add signMessageWithoutRand method for kaspa wasm --- wallet/core/src/message.rs | 30 ++++++++++++++++++++++++++++++ wallet/core/src/wasm/message.rs | 18 ++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/wallet/core/src/message.rs b/wallet/core/src/message.rs index 01dc78676b..0fff9cc5ae 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -26,6 +26,17 @@ pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32]) -> Result Ok(sig.to_vec()) } +/// Sign a message with the given private key without random +pub fn sign_message_without_rand(msg: &PersonalMessage, privkey: &[u8; 32]) -> Result, Error> { + let hash = calc_personal_message_hash(msg); + + let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice())?; + let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; + let sig: [u8; 64] = *secp256k1::SECP256K1.sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref(); + + Ok(sig.to_vec()) +} + /// Verifies signed message. /// /// Produces `Ok(())` if the signature matches the given message and [`secp256k1::Error`] @@ -75,6 +86,21 @@ mod tests { .unwrap(); verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); + verify_message(&pm, &sign_message_without_rand(&pm, &privkey).expect("sign_message failed"), &pubkey) + .expect("verify_message failed"); + } + + #[test] + fn test_basic_sign_without_rand_twice_should_get_same_signature() { + let pm = PersonalMessage("Hello Kaspa!"); + let privkey: [u8; 32] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + ]; + + let signature = sign_message_without_rand(&pm, &privkey).expect("sign_message failed"); + let signature_twice = sign_message_without_rand(&pm, &privkey).expect("sign_message failed"); + assert_eq!(signature, signature_twice); } #[test] @@ -91,6 +117,8 @@ mod tests { .unwrap(); verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); + verify_message(&pm, &sign_message_without_rand(&pm, &privkey).expect("sign_message failed"), &pubkey) + .expect("verify_message failed"); } #[test] @@ -111,6 +139,8 @@ Ut omnis magnam et accusamus earum rem impedit provident eum commodi repellat qu .unwrap(); verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); + verify_message(&pm, &sign_message_without_rand(&pm, &privkey).expect("sign_message failed"), &pubkey) + .expect("verify_message failed"); } #[test] diff --git a/wallet/core/src/wasm/message.rs b/wallet/core/src/wasm/message.rs index 25c7f399ad..41ce83cc5b 100644 --- a/wallet/core/src/wasm/message.rs +++ b/wallet/core/src/wasm/message.rs @@ -41,6 +41,24 @@ pub fn js_sign_message(value: ISignMessage) -> Result { } } +/// Signs a message with the given private key without rand +/// @category Message Signing +#[wasm_bindgen(js_name = signMessageWithoutRand)] +pub fn js_sign_message_without_rand(value: ISignMessage) -> Result { + if let Some(object) = Object::try_from(&value) { + let private_key = object.cast_into::("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_without_rand(&pm, &privkey_bytes)?; + privkey_bytes.zeroize(); + Ok(faster_hex::hex_string(sig_vec.as_slice()).into()) + } else { + Err(Error::custom("Failed to parse input")) + } +} + #[wasm_bindgen(typescript_custom_section)] const TS_MESSAGE_TYPES: &'static str = r#" /** From 9a25b94029511c740b346346d18bf85cd1d0dc50 Mon Sep 17 00:00:00 2001 From: Witter Date: Thu, 14 Nov 2024 02:08:22 +0700 Subject: [PATCH 2/5] enhance: sign message api --- cli/src/modules/message.rs | 4 ++- wallet/core/src/message.rs | 54 +++++++++++++++++++++------------ wallet/core/src/wasm/message.rs | 23 +++----------- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/cli/src/modules/message.rs b/cli/src/modules/message.rs index dce7f36790..d38624dc2b 100644 --- a/cli/src/modules/message.rs +++ b/cli/src/modules/message.rs @@ -1,5 +1,6 @@ use kaspa_addresses::Version; use kaspa_bip32::secp256k1::XOnlyPublicKey; +use kaspa_wallet_core::message::SignMessageOptions; use kaspa_wallet_core::{ account::{BIP32_ACCOUNT_KIND, KEYPAIR_ACCOUNT_KIND}, message::{sign_message, verify_message, PersonalMessage}, @@ -87,8 +88,9 @@ impl Message { let pm = PersonalMessage(message); let privkey = self.get_address_private_key(&ctx, kaspa_address).await?; + let sign_options = SignMessageOptions { no_aux_rand: false }; - let sig_result = sign_message(&pm, &privkey); + let sig_result = sign_message(&pm, &privkey, &sign_options); match sig_result { Ok(signature) => { diff --git a/wallet/core/src/message.rs b/wallet/core/src/message.rs index 0fff9cc5ae..8d4d4e4916 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -15,24 +15,28 @@ impl AsRef<[u8]> for PersonalMessage<'_> { } } -/// Sign a message with the given private key -pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32]) -> Result, Error> { - let hash = calc_personal_message_hash(msg); - - let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice())?; - let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; - let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); - - Ok(sig.to_vec()) +#[derive(Clone)] +pub struct SignMessageOptions { + /// The auxiliary randomness exists only to mitigate specific kinds of power analysis + /// side-channel attacks. Providing it definitely improves security, but omitting it + /// should not be considered dangerous, as most legacy signature schemes don't provide + /// mitigations against such attacks. To read more about the relevant discussions that + /// arose in adding this randomness please see: https://github.com/sipa/bips/issues/195 + pub no_aux_rand: bool, } -/// Sign a message with the given private key without random -pub fn sign_message_without_rand(msg: &PersonalMessage, privkey: &[u8; 32]) -> Result, Error> { +/// Sign a message with the given private key +pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32], options: &SignMessageOptions) -> Result, Error> { let hash = calc_personal_message_hash(msg); let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice())?; let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; - let sig: [u8; 64] = *secp256k1::SECP256K1.sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref(); + + let sig: [u8; 64] = if options.no_aux_rand { + *schnorr_key.sign_schnorr(msg).as_ref() + } else { + *secp256k1::Secp256k1::new().sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref() + }; Ok(sig.to_vec()) } @@ -85,8 +89,11 @@ mod tests { ]) .unwrap(); - verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); - verify_message(&pm, &sign_message_without_rand(&pm, &privkey).expect("sign_message failed"), &pubkey) + let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false }; + let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true }; + verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_aux_rand).expect("sign_message failed"), &pubkey) + .expect("verify_message failed"); + verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"), &pubkey) .expect("verify_message failed"); } @@ -98,8 +105,9 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, ]; - let signature = sign_message_without_rand(&pm, &privkey).expect("sign_message failed"); - let signature_twice = sign_message_without_rand(&pm, &privkey).expect("sign_message failed"); + let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true }; + let signature = sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"); + let signature_twice = sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"); assert_eq!(signature, signature_twice); } @@ -116,8 +124,11 @@ mod tests { ]) .unwrap(); - verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); - verify_message(&pm, &sign_message_without_rand(&pm, &privkey).expect("sign_message failed"), &pubkey) + let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false }; + let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true }; + verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_aux_rand).expect("sign_message failed"), &pubkey) + .expect("verify_message failed"); + verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"), &pubkey) .expect("verify_message failed"); } @@ -138,8 +149,11 @@ Ut omnis magnam et accusamus earum rem impedit provident eum commodi repellat qu ]) .unwrap(); - verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); - verify_message(&pm, &sign_message_without_rand(&pm, &privkey).expect("sign_message failed"), &pubkey) + let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false }; + let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true }; + verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_aux_rand).expect("sign_message failed"), &pubkey) + .expect("verify_message failed"); + verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"), &pubkey) .expect("verify_message failed"); } diff --git a/wallet/core/src/wasm/message.rs b/wallet/core/src/wasm/message.rs index 41ce83cc5b..6bc0e2eb72 100644 --- a/wallet/core/src/wasm/message.rs +++ b/wallet/core/src/wasm/message.rs @@ -14,6 +14,7 @@ const TS_MESSAGE_TYPES: &'static str = r#" export interface ISignMessage { message: string; privateKey: PrivateKey | string; + noAuxRand: boolean; } "#; @@ -30,28 +31,12 @@ pub fn js_sign_message(value: ISignMessage) -> Result { if let Some(object) = Object::try_from(&value) { let private_key = object.cast_into::("privateKey")?; let raw_msg = object.get_string("message")?; + let no_aux_rand = object.get_bool("noAuxRand")?; 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)?; - privkey_bytes.zeroize(); - Ok(faster_hex::hex_string(sig_vec.as_slice()).into()) - } else { - Err(Error::custom("Failed to parse input")) - } -} - -/// Signs a message with the given private key without rand -/// @category Message Signing -#[wasm_bindgen(js_name = signMessageWithoutRand)] -pub fn js_sign_message_without_rand(value: ISignMessage) -> Result { - if let Some(object) = Object::try_from(&value) { - let private_key = object.cast_into::("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_without_rand(&pm, &privkey_bytes)?; + let sign_options = SignMessageOptions { no_aux_rand }; + let sig_vec = sign_message(&pm, &privkey_bytes, &sign_options)?; privkey_bytes.zeroize(); Ok(faster_hex::hex_string(sig_vec.as_slice()).into()) } else { From c0757ec9627f4ec5c00595e9053078911787bc94 Mon Sep 17 00:00:00 2001 From: Witter Date: Thu, 14 Nov 2024 08:55:24 +0700 Subject: [PATCH 3/5] fix: unit test fail --- wallet/core/src/message.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/message.rs b/wallet/core/src/message.rs index 8d4d4e4916..152bf28aad 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -33,9 +33,9 @@ pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32], options: &SignMes let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?; let sig: [u8; 64] = if options.no_aux_rand { - *schnorr_key.sign_schnorr(msg).as_ref() + *secp256k1::SECP256K1.sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref() } else { - *secp256k1::Secp256k1::new().sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref() + *schnorr_key.sign_schnorr(msg).as_ref() }; Ok(sig.to_vec()) From acb30f67c67ffca7613981cd6a4e1f05fa4c9518 Mon Sep 17 00:00:00 2001 From: Witter Date: Thu, 14 Nov 2024 13:36:33 +0700 Subject: [PATCH 4/5] chore: update noAuxRand of ISignMessage --- wallet/core/src/wasm/message.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/wasm/message.rs b/wallet/core/src/wasm/message.rs index 6bc0e2eb72..372129280d 100644 --- a/wallet/core/src/wasm/message.rs +++ b/wallet/core/src/wasm/message.rs @@ -14,7 +14,7 @@ const TS_MESSAGE_TYPES: &'static str = r#" export interface ISignMessage { message: string; privateKey: PrivateKey | string; - noAuxRand: boolean; + noAuxRand?: boolean; } "#; @@ -31,7 +31,7 @@ pub fn js_sign_message(value: ISignMessage) -> Result { if let Some(object) = Object::try_from(&value) { let private_key = object.cast_into::("privateKey")?; let raw_msg = object.get_string("message")?; - let no_aux_rand = object.get_bool("noAuxRand")?; + let no_aux_rand = object.get_bool("noAuxRand").unwrap_or(false); let mut privkey_bytes = [0u8; 32]; privkey_bytes.copy_from_slice(&private_key.secret_bytes()); let pm = PersonalMessage(&raw_msg); From 51470336744b3528ffef548dbf482890b0f8fd67 Mon Sep 17 00:00:00 2001 From: Witter Date: Thu, 14 Nov 2024 14:12:24 +0700 Subject: [PATCH 5/5] chore: add sign message demo for noAuxRand --- wasm/examples/nodejs/javascript/general/message-signing.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wasm/examples/nodejs/javascript/general/message-signing.js b/wasm/examples/nodejs/javascript/general/message-signing.js index ed12afd451..832af2ca67 100644 --- a/wasm/examples/nodejs/javascript/general/message-signing.js +++ b/wasm/examples/nodejs/javascript/general/message-signing.js @@ -12,8 +12,8 @@ let message = 'Hello Kaspa!'; let privkey = 'b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef'; let pubkey = 'dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659'; -function runDemo(message, privateKey, publicKey) { - let signature = signMessage({message, privateKey}); +function runDemo(message, privateKey, publicKey, noAuxRand) { + let signature = signMessage({message, privateKey, noAuxRand}); console.info(`Message: ${message} => Signature: ${signature}`); @@ -26,5 +26,7 @@ function runDemo(message, privateKey, publicKey) { // Using strings: runDemo(message, privkey, pubkey); +runDemo(message, privkey, pubkey, true); // Using Objects: runDemo(message, new PrivateKey(privkey), new PublicKey(pubkey)); +runDemo(message, new PrivateKey(privkey), new PublicKey(pubkey), true);