Skip to content

Commit

Permalink
Custom input signatureScript ability (kaspanet#69)
Browse files Browse the repository at this point in the history
* Refactorize some addresses and Script related parts

* A ScriptBuilder example on TypeScript

* addOps Opcodes are now BinaryT

* Move txscript bindings to relevant folders

* Sort lines of deps and imports

* Lint

* Prototype of custom sighash operations

* Experimental

* Add SighashType enum and option on SignInput

* Format and a small fix

* Clippy
  • Loading branch information
KaffinPX authored Jul 16, 2024
1 parent 1be1002 commit 4c0b148
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 6 deletions.
2 changes: 2 additions & 0 deletions consensus/core/src/hashing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod header;
pub mod sighash;
pub mod sighash_type;
pub mod tx;
#[cfg(feature = "wasm32-sdk")]
pub mod wasm;

pub trait HasherExtensions {
/// Writes the len as u64 little endian bytes
Expand Down
28 changes: 28 additions & 0 deletions consensus/core/src/hashing/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 {
All,
None,
Single,
AllAnyOneCanPay,
NoneAnyOneCanPay,
SingleAnyOneCanPay,
}

impl From<SighashType> for SigHashType {
fn from(sighash_type: SighashType) -> SigHashType {
match sighash_type {
SighashType::All => sighash_type::SIG_HASH_ALL,
SighashType::None => sighash_type::SIG_HASH_NONE,
SighashType::Single => sighash_type::SIG_HASH_SINGLE,
SighashType::AllAnyOneCanPay => sighash_type::SIG_HASH_ANY_ONE_CAN_PAY,
SighashType::NoneAnyOneCanPay => SigHashType(sighash_type::SIG_HASH_NONE.0 | sighash_type::SIG_HASH_ANY_ONE_CAN_PAY.0),
SighashType::SingleAnyOneCanPay => SigHashType(sighash_type::SIG_HASH_SINGLE.0 | sighash_type::SIG_HASH_ANY_ONE_CAN_PAY.0),
}
}
}
2 changes: 1 addition & 1 deletion wallet/core/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ pub trait DerivationCapableAccount: Account {

let mut stream = generator.stream();
while let Some(transaction) = stream.try_next().await? {
transaction.try_sign_with_keys(&keys)?;
transaction.try_sign_with_keys(&keys, None)?;
let id = transaction.try_submit(&rpc).await?;
if let Some(notifier) = notifier {
notifier(index, aggregate_utxo_count, balance, Some(id));
Expand Down
42 changes: 39 additions & 3 deletions wallet/core/src/tx/generator/pending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::result::Result;
use crate::rpc::DynRpcApi;
use crate::tx::{DataKind, Generator};
use crate::utxo::{UtxoContext, UtxoEntryId, UtxoEntryReference};
use kaspa_consensus_core::sign::sign_with_multiple_v2;
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::tx::{SignableTransaction, Transaction, TransactionId};
use kaspa_rpc_core::{RpcTransaction, RpcTransactionId};

Expand Down Expand Up @@ -223,9 +225,43 @@ impl PendingTransaction {
Ok(())
}

pub fn try_sign_with_keys(&self, privkeys: &[[u8; 32]]) -> Result<()> {
pub fn sign_input(&self, input_index: usize, private_key: &[u8; 32], hash_type: SigHashType) -> Result<Vec<u8>> {
// TODO: Move to sign.rs
let mutable_tx = self.inner.signable_tx.lock()?.clone();
let signed_tx = sign_with_multiple_v2(mutable_tx, privkeys).fully_signed()?;
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 <SIGNATURE+SIGHASH_TYPE> (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())
}

pub fn fill_input(&self, input_index: usize, signature_script: Vec<u8>) -> Result<()> {
let mut mutable_tx = self.inner.signable_tx.lock()?.clone();
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<bool>) -> Result<()> {
let mutable_tx = self.inner.signable_tx.lock()?.clone();
let signed = sign_with_multiple_v2(mutable_tx, privkeys);

let signed_tx = match signed {
Signed::Fully(tx) => tx,
Signed::Partially(_) => {
if check_fully_signed.unwrap_or(true) {
signed.fully_signed()?
} else {
signed.unwrap()
}
}
};

*self.inner.signable_tx.lock().unwrap() = signed_tx;
Ok(())
}
Expand Down
19 changes: 17 additions & 2 deletions wallet/core/src/wasm/tx/generator/pending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use crate::tx::generator as native;
use crate::wasm::PrivateKeyArrayT;
use kaspa_consensus_client::{numeric, string};
use kaspa_consensus_client::{Transaction, TransactionT};
use kaspa_consensus_core::hashing::wasm::SighashType;
use kaspa_wallet_keys::privatekey::PrivateKey;
use kaspa_wasm_core::types::{BinaryT, HexString};
use kaspa_wrpc_wasm::RpcClient;

/// @category Wallet SDK
Expand Down Expand Up @@ -70,16 +72,29 @@ 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<SighashType>) -> Result<HexString> {
let signature =
self.inner.sign_input(input_index.into(), &private_key.secret_bytes(), sighash_type.unwrap_or(SighashType::All).into())?;

Ok(signature.to_hex().into())
}

#[wasm_bindgen(js_name = fillInput)]
pub fn fill_input(&self, input_index: u8, signature_script: BinaryT) -> Result<()> {
self.inner.fill_input(input_index.into(), signature_script.try_as_vec_u8()?)
}

/// 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) -> Result<()> {
pub fn sign(&self, js_value: PrivateKeyArrayT, check_fully_signed: Option<bool>) -> Result<()> {
if let Ok(keys) = js_value.dyn_into::<Array>() {
let keys = keys
.iter()
.map(PrivateKey::try_cast_from)
.collect::<std::result::Result<Vec<_>, kaspa_wallet_keys::error::Error>>()?;
let mut keys = keys.iter().map(|key| key.as_ref().secret_bytes()).collect::<Vec<_>>();
self.inner.try_sign_with_keys(&keys)?;
self.inner.try_sign_with_keys(&keys, check_fully_signed)?;
keys.zeroize();
Ok(())
} else {
Expand Down

0 comments on commit 4c0b148

Please sign in to comment.