diff --git a/src/wasm/key_utils.rs b/src/wasm/key_utils.rs index a90745a8..fbe4f9ea 100644 --- a/src/wasm/key_utils.rs +++ b/src/wasm/key_utils.rs @@ -32,7 +32,7 @@ use crate::{ use blake2::Digest; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; -use tari_utilities::hex::Hex; +use tari_utilities::hex::{from_hex, Hex}; use wasm_bindgen::prelude::*; #[derive(Serialize, Deserialize)] @@ -87,15 +87,62 @@ pub fn sign(private_key: &str, msg: &str) -> JsValue { return JsValue::from_serde(&result).unwrap(); }, }; - sign_with_key(&k, msg, &mut result); + sign_message_with_key(&k, msg, None, &mut result); JsValue::from_serde(&result).unwrap() } -#[allow(non_snake_case)] -pub(crate) fn sign_with_key(k: &RistrettoSecretKey, msg: &str, result: &mut SignResult) { - let (r, R) = RistrettoPublicKey::random_keypair(&mut OsRng); +/// Generate a Schnorr signature of a challenge that has already been hashed using the given private +/// key and a specified private nonce. DO NOT reuse nonces. This method is provide for cases where a +/// public nonce has been used +/// in the message. +#[wasm_bindgen] +pub fn sign_challenge_with_nonce(private_key: &str, private_nonce: &str, challenge_as_hex: &str) -> JsValue { + let mut result = SignResult::default(); + let k = match RistrettoSecretKey::from_hex(private_key) { + Ok(k) => k, + _ => { + result.error = "Invalid private key".to_string(); + return JsValue::from_serde(&result).unwrap(); + }, + }; + let r = match RistrettoSecretKey::from_hex(private_nonce) { + Ok(r) => r, + _ => { + result.error = "Invalid private nonce".to_string(); + return JsValue::from_serde(&result).unwrap(); + }, + }; + + let e = match from_hex(challenge_as_hex) { + Ok(e) => e, + _ => { + result.error = "Challenge was not valid HEX".to_string(); + return JsValue::from_serde(&result).unwrap(); + }, + }; + sign_with_key(&k, &e, Some(&r), &mut result); + JsValue::from_serde(&result).unwrap() +} + +pub(crate) fn sign_message_with_key( + k: &RistrettoSecretKey, + msg: &str, + r: Option<&RistrettoSecretKey>, + result: &mut SignResult, +) +{ let e = Blake256::digest(msg.as_bytes()); - let sig = match RistrettoSchnorr::sign(k.clone(), r, e.as_slice()) { + sign_with_key(k, e.as_slice(), r, result) +} + +#[allow(non_snake_case)] +pub(crate) fn sign_with_key(k: &RistrettoSecretKey, e: &[u8], r: Option<&RistrettoSecretKey>, result: &mut SignResult) { + let (r, R) = match r { + Some(r) => (r.clone(), RistrettoPublicKey::from_secret_key(r)), + None => RistrettoPublicKey::random_keypair(&mut OsRng), + }; + + let sig = match RistrettoSchnorr::sign(k.clone(), r, e) { Ok(s) => s, Err(e) => { result.error = format!("Could not create signature. {}", e.to_string()); diff --git a/src/wasm/keyring.rs b/src/wasm/keyring.rs index 61e14e7d..29c79ddf 100644 --- a/src/wasm/keyring.rs +++ b/src/wasm/keyring.rs @@ -32,7 +32,7 @@ use crate::{ }, wasm::{ commitments::CommitmentResult, - key_utils::{sign_with_key, SignResult}, + key_utils::{sign_message_with_key, SignResult}, }, }; use rand::rngs::OsRng; @@ -101,7 +101,35 @@ impl KeyRing { return JsValue::from_serde(&result).unwrap(); } let k = k.unwrap(); - sign_with_key(&k.0, msg, &mut result); + sign_message_with_key(&k.0, msg, None, &mut result); + JsValue::from_serde(&result).unwrap() + } + + /// Sign a message using a private key and a specific nonce + /// + /// Use can use a key in the keyring to generate a digital signature. To create the signature, the caller must + /// provide the `id` associated with the key, the message to sign, and a `nonce_id`. *Do not* reuse nonces. + /// This function is provided because in some signature schemes require the public nonce to be + /// part of the message. + /// + /// The return type is pretty unRust-like, but is structured to more closely model a JSON object. + /// + /// `keys::check_signature` is used to verify signatures. + pub fn sign_with_nonce(&self, id: &str, nonce_id: &str, msg: &str) -> JsValue { + let mut result = SignResult::default(); + let k = self.keys.get(id); + if k.is_none() { + result.error = format!("Private key for '{}' does not exist", id); + return JsValue::from_serde(&result).unwrap(); + } + let k = k.unwrap(); + let nonce = self.keys.get(nonce_id); + if nonce.is_none() { + result.error = format!("Private nonce for `{}` does not exist", nonce_id); + return JsValue::from_serde(&result).unwrap(); + } + let nonce = nonce.unwrap(); + sign_message_with_key(&k.0, msg, Some(&nonce.0), &mut result); JsValue::from_serde(&result).unwrap() } diff --git a/wasm-demo.js b/wasm-demo.js index 6e5c7211..d27a6cd8 100644 --- a/wasm-demo.js +++ b/wasm-demo.js @@ -22,7 +22,7 @@ // // If you get a "module not found" error, see README.md for details on how to generate the node package -let tari_crypto = require('./pkg'); +let tari_crypto = require('./tari_js'); let assert = require('assert'); @@ -58,6 +58,25 @@ if (sig.error) { } } +// Sign with nonce +console.log("Signing message with predetermined nonce"); +let nonce = keys.new_key("Nonce"); +sig = keys.sign_with_nonce("Alice", "Nonce","Hello Tari"); +if (sig.error) { + console.log(`Error getting signature ${sig.error}`); +} else { + console.log('Signature:', sig); + console.log("Verifying signature.."); + let pubkey = keys.public_key("Alice"); + console.log(`Pubkey: ${pubkey}`); + let check = tari_crypto.check_signature(sig.public_nonce, sig.signature, pubkey, "Hello Tari"); + if (check.result === true) { + console.log("Signature is valid!"); + } else { + console.log(`Invalid signature: ${check.error}`); + } +} + // Commitments const v = BigInt(10200300); const k = keys.private_key("Bob");