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 01dc78676b..152bf28aad 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -15,13 +15,28 @@ impl AsRef<[u8]> for PersonalMessage<'_> { } } +#[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 -pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32]) -> Result, Error> { +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] = *schnorr_key.sign_schnorr(msg).as_ref(); + + let sig: [u8; 64] = if options.no_aux_rand { + *secp256k1::SECP256K1.sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref() + } else { + *schnorr_key.sign_schnorr(msg).as_ref() + }; Ok(sig.to_vec()) } @@ -74,7 +89,26 @@ mod tests { ]) .unwrap(); - verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); + 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"); + } + + #[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 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); } #[test] @@ -90,7 +124,12 @@ mod tests { ]) .unwrap(); - verify_message(&pm, &sign_message(&pm, &privkey).expect("sign_message failed"), &pubkey).expect("verify_message failed"); + 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"); } #[test] @@ -110,7 +149,12 @@ 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"); + 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"); } #[test] diff --git a/wallet/core/src/wasm/message.rs b/wallet/core/src/wasm/message.rs index 25c7f399ad..372129280d 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,10 +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").unwrap_or(false); 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)?; + 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 { 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);