From f4b9db2b74008a472577e7287f77015dc7dbbfc8 Mon Sep 17 00:00:00 2001 From: KaffinPX Date: Wed, 24 Jul 2024 04:22:58 +0300 Subject: [PATCH 1/7] ``signTransactionInput`` and move sign_input to its proper location --- consensus/core/src/sign.rs | 15 ++++++++++++++- wallet/core/src/tx/generator/pending.rs | 13 ++----------- wallet/core/src/wasm/signer.rs | 23 ++++++++++++++++++++++- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/consensus/core/src/sign.rs b/consensus/core/src/sign.rs index dee0d3844..174f68971 100644 --- a/consensus/core/src/sign.rs +++ b/consensus/core/src/sign.rs @@ -1,7 +1,7 @@ use crate::{ hashing::{ sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, - sighash_type::SIG_HASH_ALL, + sighash_type::{SigHashType, SIG_HASH_ALL}, }, tx::SignableTransaction, }; @@ -153,6 +153,19 @@ pub fn sign_with_multiple_v2(mut mutable_tx: SignableTransaction, privkeys: &[[u } } +/// Sign a transaction input with a sighash_type using schnorr +pub fn sign_input(tx: SignableTransaction, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Vec { + let mut reused_values = SigHashReusedValues::new(); + + let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), input_index, hash_type, &mut reused_values); + let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); + let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, private_key).unwrap(); + let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); + + // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) + std::iter::once(65u8).chain(sig).chain([hash_type.to_u8()]).collect() +} + pub fn verify(tx: &impl crate::tx::VerifiableTransaction) -> Result<(), Error> { let mut reused_values = SigHashReusedValues::new(); for (i, (input, entry)) in tx.populated_inputs().enumerate() { diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index 7d07af74e..a671fd343 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -8,9 +8,8 @@ use crate::result::Result; use crate::rpc::DynRpcApi; use crate::tx::{DataKind, Generator}; use crate::utxo::{UtxoContext, UtxoEntryId, UtxoEntryReference}; -use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValues}; use kaspa_consensus_core::hashing::sighash_type::SigHashType; -use kaspa_consensus_core::sign::{sign_with_multiple_v2, Signed}; +use kaspa_consensus_core::sign::{sign_input, sign_with_multiple_v2, Signed}; use kaspa_consensus_core::tx::{SignableTransaction, Transaction, TransactionId}; use kaspa_rpc_core::{RpcTransaction, RpcTransactionId}; @@ -226,17 +225,9 @@ impl PendingTransaction { } pub fn sign_input(&self, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Result> { - // TODO: Move to sign.rs let mutable_tx = self.inner.signable_tx.lock()?.clone(); - let mut reused_values = SigHashReusedValues::new(); - let hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), input_index, hash_type, &mut reused_values); - let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); - let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, private_key).unwrap(); - let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); - - // This represents OP_DATA_65 (since signature length is 64 bytes and SIGHASH_TYPE is one byte) - Ok(std::iter::once(65u8).chain(sig).chain([hash_type.to_u8()]).collect()) + Ok(sign_input(mutable_tx, input_index, private_key, hash_type)) } pub fn fill_input(&self, input_index: usize, signature_script: Vec) -> Result<()> { diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index e2ff8e6fb..e40c20430 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -2,10 +2,13 @@ use crate::imports::*; use crate::result::Result; use js_sys::Array; use kaspa_consensus_client::{sign_with_multiple_v3, Transaction}; -use kaspa_consensus_core::tx::PopulatedTransaction; +use kaspa_consensus_core::hashing::wasm::SighashType; +use kaspa_consensus_core::sign::sign_input; +use kaspa_consensus_core::tx::{PopulatedTransaction, SignableTransaction}; use kaspa_consensus_core::{hashing::sighash_type::SIG_HASH_ALL, sign::verify}; use kaspa_hashes::Hash; use kaspa_wallet_keys::privatekey::PrivateKey; +use kaspa_wasm_core::types::HexString; use serde_wasm_bindgen::from_value; #[wasm_bindgen] @@ -64,6 +67,24 @@ pub fn sign(tx: Transaction, privkeys: &[[u8; 32]]) -> Result { Ok(sign_with_multiple_v3(tx, privkeys)?.unwrap()) } +/// `signTransactionInput()` is a helper function to sign a transaction input with a specific SigHash type using a private key. +/// @category Wallet SDK +#[wasm_bindgen(js_name = "signTransactionInput")] +pub fn sign_transaction_input( + tx: Transaction, + input_index: u8, + private_key: &PrivateKey, + sighash_type: Option, +) -> Result { + let (cctx, _) = tx.tx_and_utxos(); + let mutable_tx = SignableTransaction::new(cctx); + + let signature = + sign_input(mutable_tx, input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into()); + + Ok(signature.to_hex().into()) +} + /// @category Wallet SDK #[wasm_bindgen(js_name=signScriptHash)] pub fn sign_script_hash(script_hash: JsValue, privkey: &PrivateKey) -> Result { From ff72ca6f496e0ed59729e75338e84e092e1a5a10 Mon Sep 17 00:00:00 2001 From: KaffinPX Date: Wed, 24 Jul 2024 15:54:12 +0300 Subject: [PATCH 2/7] Fix typedoc warnings left from old PR --- consensus/core/src/hashing/wasm.rs | 1 - crypto/txscript/src/wasm/builder.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/consensus/core/src/hashing/wasm.rs b/consensus/core/src/hashing/wasm.rs index 1c3b51a7a..4c9c94b22 100644 --- a/consensus/core/src/hashing/wasm.rs +++ b/consensus/core/src/hashing/wasm.rs @@ -2,7 +2,6 @@ use super::sighash_type::{self, SigHashType}; use wasm_bindgen::prelude::*; /// Kaspa Sighash types allowed by consensus -/// @see {@link signInput} /// @category Consensus #[wasm_bindgen] pub enum SighashType { diff --git a/crypto/txscript/src/wasm/builder.rs b/crypto/txscript/src/wasm/builder.rs index 8a6688fd9..57c6b8b4f 100644 --- a/crypto/txscript/src/wasm/builder.rs +++ b/crypto/txscript/src/wasm/builder.rs @@ -15,8 +15,6 @@ use workflow_wasm::prelude::*; /// data pushes which would exceed the maximum allowed script engine limits and /// are therefore guaranteed not to execute will not be pushed and will result in /// the Script function returning an error. -/// -/// @see {@link Opcode} /// @category Consensus #[derive(Clone)] #[wasm_bindgen(inspectable)] From 870a41eabf0df472c5b2bee654453dae89e5ec57 Mon Sep 17 00:00:00 2001 From: KaffinPX <73744616+KaffinPX@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:17:22 +0300 Subject: [PATCH 3/7] createInputSignature --- wallet/core/src/wasm/signer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index e40c20430..106988a3c 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -67,10 +67,10 @@ pub fn sign(tx: Transaction, privkeys: &[[u8; 32]]) -> Result { Ok(sign_with_multiple_v3(tx, privkeys)?.unwrap()) } -/// `signTransactionInput()` is a helper function to sign a transaction input with a specific SigHash type using a private key. +/// `createInputSignature()` is a helper function to sign a transaction input with a specific SigHash type using a private key. /// @category Wallet SDK -#[wasm_bindgen(js_name = "signTransactionInput")] -pub fn sign_transaction_input( +#[wasm_bindgen(js_name = "createInputSignature")] +pub fn create_input_signature( tx: Transaction, input_index: u8, private_key: &PrivateKey, From ad4956506f1f1cdb876ca67f9d80f265367cf38c Mon Sep 17 00:00:00 2001 From: KaffinPX Date: Sat, 3 Aug 2024 03:02:25 +0300 Subject: [PATCH 4/7] Fix createInputSignature and improve PendingTransaction Inputs DX --- consensus/core/src/sign.rs | 8 ++++---- wallet/core/src/tx/generator/pending.rs | 19 +++++++++++++++++-- wallet/core/src/wasm/signer.rs | 8 ++++---- wallet/core/src/wasm/tx/generator/pending.rs | 13 ++++++++++--- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/consensus/core/src/sign.rs b/consensus/core/src/sign.rs index 174f68971..a40b949e3 100644 --- a/consensus/core/src/sign.rs +++ b/consensus/core/src/sign.rs @@ -3,7 +3,7 @@ use crate::{ sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, sighash_type::{SigHashType, SIG_HASH_ALL}, }, - tx::SignableTransaction, + tx::{SignableTransaction, VerifiableTransaction}, }; use itertools::Itertools; use std::collections::BTreeMap; @@ -154,10 +154,10 @@ pub fn sign_with_multiple_v2(mut mutable_tx: SignableTransaction, privkeys: &[[u } /// Sign a transaction input with a sighash_type using schnorr -pub fn sign_input(tx: SignableTransaction, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Vec { +pub fn sign_input(tx: &impl VerifiableTransaction, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Vec { let mut reused_values = SigHashReusedValues::new(); - let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), input_index, hash_type, &mut reused_values); + let hash = calc_schnorr_signature_hash(tx, input_index, hash_type, &mut reused_values); let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, private_key).unwrap(); let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref(); @@ -166,7 +166,7 @@ pub fn sign_input(tx: SignableTransaction, input_index: usize, private_key: &[u8 std::iter::once(65u8).chain(sig).chain([hash_type.to_u8()]).collect() } -pub fn verify(tx: &impl crate::tx::VerifiableTransaction) -> Result<(), Error> { +pub fn verify(tx: &impl VerifiableTransaction) -> Result<(), Error> { let mut reused_values = SigHashReusedValues::new(); for (i, (input, entry)) in tx.populated_inputs().enumerate() { if input.signature_script.is_empty() { diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index a671fd343..c5171c1f3 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -224,10 +224,11 @@ impl PendingTransaction { Ok(()) } - pub fn sign_input(&self, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Result> { + pub fn create_input_signature(&self, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Result> { let mutable_tx = self.inner.signable_tx.lock()?.clone(); + let verifiable_tx = mutable_tx.as_verifiable(); - Ok(sign_input(mutable_tx, input_index, private_key, hash_type)) + Ok(sign_input(&verifiable_tx, input_index, private_key, hash_type)) } pub fn fill_input(&self, input_index: usize, signature_script: Vec) -> Result<()> { @@ -238,6 +239,20 @@ impl PendingTransaction { Ok(()) } + pub fn sign_input(&self, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Result<()> { + let mut mutable_tx = self.inner.signable_tx.lock()?.clone(); + + let signature_script = { + let verifiable_tx = &mutable_tx.as_verifiable(); + sign_input(verifiable_tx, input_index, private_key, hash_type) + }; + + mutable_tx.tx.inputs[input_index].signature_script = signature_script; + *self.inner.signable_tx.lock().unwrap() = mutable_tx; + + Ok(()) + } + pub fn try_sign_with_keys(&self, privkeys: &[[u8; 32]], check_fully_signed: Option) -> Result<()> { let mutable_tx = self.inner.signable_tx.lock()?.clone(); let signed = sign_with_multiple_v2(mutable_tx, privkeys); diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index 106988a3c..39e4c6a09 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -4,7 +4,7 @@ use js_sys::Array; use kaspa_consensus_client::{sign_with_multiple_v3, Transaction}; use kaspa_consensus_core::hashing::wasm::SighashType; use kaspa_consensus_core::sign::sign_input; -use kaspa_consensus_core::tx::{PopulatedTransaction, SignableTransaction}; +use kaspa_consensus_core::tx::PopulatedTransaction; use kaspa_consensus_core::{hashing::sighash_type::SIG_HASH_ALL, sign::verify}; use kaspa_hashes::Hash; use kaspa_wallet_keys::privatekey::PrivateKey; @@ -76,11 +76,11 @@ pub fn create_input_signature( private_key: &PrivateKey, sighash_type: Option, ) -> Result { - let (cctx, _) = tx.tx_and_utxos(); - let mutable_tx = SignableTransaction::new(cctx); + let (cctx, utxos) = tx.tx_and_utxos(); + let populated_transaction = PopulatedTransaction::new(&cctx, utxos); let signature = - sign_input(mutable_tx, input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into()); + sign_input(&populated_transaction, input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into()); Ok(signature.to_hex().into()) } diff --git a/wallet/core/src/wasm/tx/generator/pending.rs b/wallet/core/src/wasm/tx/generator/pending.rs index 2257440d9..8869aa677 100644 --- a/wallet/core/src/wasm/tx/generator/pending.rs +++ b/wallet/core/src/wasm/tx/generator/pending.rs @@ -72,10 +72,10 @@ impl PendingTransaction { self.inner.utxo_entries().values().map(|utxo_entry| JsValue::from(utxo_entry.clone())).collect() } - #[wasm_bindgen(js_name = signInput)] - pub fn sign_input(&self, input_index: u8, private_key: &PrivateKey, sighash_type: Option) -> Result { + #[wasm_bindgen(js_name = createInputSignature)] + pub fn create_input_signature(&self, input_index: u8, private_key: &PrivateKey, sighash_type: Option) -> Result { let signature = - self.inner.sign_input(input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into())?; + self.inner.create_input_signature(input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into())?; Ok(signature.to_hex().into()) } @@ -85,6 +85,13 @@ impl PendingTransaction { self.inner.fill_input(input_index.into(), signature_script.try_as_vec_u8()?) } + #[wasm_bindgen(js_name = signInput)] + pub fn sign_input(&self, input_index: u8, private_key: &PrivateKey, sighash_type: Option) -> Result<()> { + self.inner.sign_input(input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into())?; + + Ok(()) + } + /// Sign transaction with supplied [`Array`] or [`PrivateKey`] or an array of /// raw private key bytes (encoded as `Uint8Array` or as hex strings) pub fn sign(&self, js_value: PrivateKeyArrayT, check_fully_signed: Option) -> Result<()> { From 15ff4284b6696ddddc89e8d64922ac8a4fee1503 Mon Sep 17 00:00:00 2001 From: KaffinPX Date: Sat, 3 Aug 2024 03:04:23 +0300 Subject: [PATCH 5/7] Format --- wallet/core/src/tx/generator/pending.rs | 6 +++--- wallet/core/src/wasm/signer.rs | 8 ++++++-- wallet/core/src/wasm/tx/generator/pending.rs | 14 +++++++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index c5171c1f3..ad7e2e40f 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -246,11 +246,11 @@ impl PendingTransaction { let verifiable_tx = &mutable_tx.as_verifiable(); sign_input(verifiable_tx, input_index, private_key, hash_type) }; - + mutable_tx.tx.inputs[input_index].signature_script = signature_script; *self.inner.signable_tx.lock().unwrap() = mutable_tx; - - Ok(()) + + Ok(()) } pub fn try_sign_with_keys(&self, privkeys: &[[u8; 32]], check_fully_signed: Option) -> Result<()> { diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index 39e4c6a09..7f18dcb6a 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -79,8 +79,12 @@ pub fn create_input_signature( let (cctx, utxos) = tx.tx_and_utxos(); let populated_transaction = PopulatedTransaction::new(&cctx, utxos); - let signature = - sign_input(&populated_transaction, input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into()); + let signature = sign_input( + &populated_transaction, + input_index.into(), + &private_key.secret_bytes(), + sighash_type.unwrap_or(SighashType::All).into(), + ); Ok(signature.to_hex().into()) } diff --git a/wallet/core/src/wasm/tx/generator/pending.rs b/wallet/core/src/wasm/tx/generator/pending.rs index 8869aa677..f3af3299e 100644 --- a/wallet/core/src/wasm/tx/generator/pending.rs +++ b/wallet/core/src/wasm/tx/generator/pending.rs @@ -73,9 +73,17 @@ impl PendingTransaction { } #[wasm_bindgen(js_name = createInputSignature)] - pub fn create_input_signature(&self, input_index: u8, private_key: &PrivateKey, sighash_type: Option) -> Result { - let signature = - self.inner.create_input_signature(input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into())?; + pub fn create_input_signature( + &self, + input_index: u8, + private_key: &PrivateKey, + sighash_type: Option, + ) -> Result { + let signature = self.inner.create_input_signature( + input_index.into(), + &private_key.secret_bytes(), + sighash_type.unwrap_or(SighashType::All).into(), + )?; Ok(signature.to_hex().into()) } From 3773b3b2b0a290db1162eba7e19b9f79acce3506 Mon Sep 17 00:00:00 2001 From: KaffinPX Date: Sat, 3 Aug 2024 03:26:30 +0300 Subject: [PATCH 6/7] A small Omega change applied to existing code --- wallet/core/src/wasm/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index 8d78741eb..6325f25ac 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -76,7 +76,7 @@ pub fn create_input_signature( private_key: &PrivateKey, sighash_type: Option, ) -> Result { - let (cctx, utxos) = tx.tx_and_utxos(); + let (cctx, utxos) = tx.tx_and_utxos()?; let populated_transaction = PopulatedTransaction::new(&cctx, utxos); let signature = sign_input( From e5d1de56ccfbd8f018a2714eb668099c1b1ba517 Mon Sep 17 00:00:00 2001 From: KaffinPX Date: Sat, 3 Aug 2024 16:04:30 +0300 Subject: [PATCH 7/7] Pass reference of transaction in createInputSignature --- wallet/core/src/wasm/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index 6325f25ac..748a95a55 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -71,7 +71,7 @@ pub fn sign(tx: Transaction, privkeys: &[[u8; 32]]) -> Result { /// @category Wallet SDK #[wasm_bindgen(js_name = "createInputSignature")] pub fn create_input_signature( - tx: Transaction, + tx: &Transaction, input_index: u8, private_key: &PrivateKey, sighash_type: Option,