From d1f6ce782415e4371cf193ce61d7726880d0f3f0 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 2 Jun 2024 11:53:20 +0300 Subject: [PATCH] extractor --- Cargo.lock | 2 + consensus/core/src/tx.rs | 2 +- wallet/pskt/Cargo.toml | 3 +- wallet/pskt/src/lib.rs | 181 +++++++++++++++++++++++++++++---------- wallet/pskt/src/role.rs | 2 +- 5 files changed, 143 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50208ca1bd..9a9fdcec0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3625,6 +3625,8 @@ dependencies = [ "derive_builder", "kaspa-bip32", "kaspa-consensus-core", + "kaspa-txscript", + "kaspa-txscript-errors", "secp256k1", "serde", "serde-value", diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index 33911ce310..c2d3ba2e0b 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -29,7 +29,7 @@ pub type TransactionId = kaspa_hashes::Hash; /// score of the block that accepts the tx, its public key script, and how /// much it pays. /// @category Consensus -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] #[wasm_bindgen(inspectable, js_name = TransactionUtxoEntry)] pub struct UtxoEntry { diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index aae0ab4b76..5a7aaab3c0 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -17,7 +17,8 @@ crate-type = ["cdylib", "lib"] [dependencies] kaspa-bip32.workspace = true kaspa-consensus-core.workspace = true - +kaspa-txscript.workspace = true +kaspa-txscript-errors.workspace = true derive_builder.workspace = true secp256k1.workspace = true serde.workspace = true diff --git a/wallet/pskt/src/lib.rs b/wallet/pskt/src/lib.rs index 6ccc4eadfd..a7cf73ed7b 100644 --- a/wallet/pskt/src/lib.rs +++ b/wallet/pskt/src/lib.rs @@ -1,9 +1,4 @@ use kaspa_bip32::{secp256k1, DerivationPath, KeyFingerprint}; -use kaspa_consensus_core::{ - hashing::sighash_type::SigHashType, - subnets::SUBNETWORK_ID_NATIVE, - tx::{SignableTransaction, Transaction, TransactionInput, TransactionOutput}, -}; use std::{collections::BTreeMap, fmt::Display, fmt::Formatter, future::Future, marker::PhantomData, ops::Deref}; mod error; @@ -15,10 +10,29 @@ mod output; mod role; mod utils; -use crate::role::Finalizer; +use crate::role::{Extractor, Finalizer}; pub use error::Error; pub use global::Global; pub use input::Input; +use kaspa_consensus_core::{ + hashing::{ + sighash::SigHashReusedValues, + sighash_type::SigHashType + }, + subnets::SUBNETWORK_ID_NATIVE, + tx::{ + SignableTransaction, + Transaction, + TransactionInput, + TransactionOutput, + MutableTransaction, + TransactionId + } +}; +use kaspa_txscript::{ + caches::Cache, + TxScriptEngine +}; pub use output::Output; pub use role::{Combiner, Constructor, Creator, Signer, Updater}; @@ -60,6 +74,7 @@ pub enum Signature { pub struct PSKT { inner_pskt: Inner, role: PhantomData, + id: Option, } impl Deref for PSKT { @@ -70,15 +85,46 @@ impl Deref for PSKT { } } -impl Default for PSKT { - fn default() -> Self { - PSKT { inner_pskt: Default::default(), role: Default::default() } +impl PSKT { + fn unsigned_tx(&self) -> SignableTransaction { + let tx = Transaction::new_non_finalized( + self.global.tx_version, + self.inputs + .iter() + .map(|Input { previous_outpoint, sequence, sig_op_count, .. }| TransactionInput { + previous_outpoint: *previous_outpoint, + signature_script: vec![], + sequence: sequence.unwrap_or(u64::MAX), + sig_op_count: sig_op_count.unwrap_or(0), + }) + .collect(), + self.outputs + .iter() + .map(|Output { amount, script_public_key, .. }: &Output| TransactionOutput { + value: *amount, + script_public_key: script_public_key.clone(), + }) + .collect(), + 0, + SUBNETWORK_ID_NATIVE, + 0, + vec![], + ); + + let entries = self.inputs.iter().filter_map(|Input { utxo_entry, .. }| utxo_entry.clone()).collect(); + SignableTransaction::with_entries(tx, entries) + } + + fn calculate_id_internal(&self) -> TransactionId { + let mut tx = self.unsigned_tx().tx.clone(); + tx.finalize(); + tx.id() } } -impl PSKT { - pub fn determine_lock_time(&self) -> u64 { - self.inputs.iter().map(|input: &Input| input.min_time).max().unwrap_or(self.global.fallback_lock_time).unwrap_or(0) +impl Default for PSKT { + fn default() -> Self { + PSKT { inner_pskt: Default::default(), role: Default::default(), id: None } } } @@ -103,7 +149,7 @@ impl PSKT { } pub fn constructor(self) -> PSKT { - PSKT { inner_pskt: self.inner_pskt, role: Default::default() } + PSKT { inner_pskt: self.inner_pskt, role: Default::default(), id: None } } } @@ -138,7 +184,7 @@ impl PSKT { /// Returns a PSBT [`Updater`] once construction is completed. pub fn updater(self) -> PSKT { let pskt = self.no_more_inputs().no_more_outputs(); - PSKT { inner_pskt: pskt.inner_pskt, role: Default::default() } + PSKT { inner_pskt: pskt.inner_pskt, role: Default::default(), id: None } } pub fn signer(self) -> PSKT { @@ -153,39 +199,11 @@ impl PSKT { } pub fn signer(self) -> PSKT { - PSKT { inner_pskt: self.inner_pskt, role: Default::default() } + PSKT { inner_pskt: self.inner_pskt, role: Default::default(), id: None } } } impl PSKT { - fn unsigned_tx(&self) -> SignableTransaction { - let tx = Transaction::new( - self.global.tx_version, - self.inputs - .iter() - .map(|Input { previous_outpoint, sequence, sig_op_count, .. }| TransactionInput { - previous_outpoint: *previous_outpoint, - signature_script: vec![], - sequence: sequence.unwrap_or(u64::MAX), - sig_op_count: sig_op_count.unwrap_or(0), - }) - .collect(), - self.outputs - .iter() - .map(|Output { amount, script_public_key, .. }: &Output| TransactionOutput { - value: *amount, - script_public_key: script_public_key.clone(), - }) - .collect(), - 0, - SUBNETWORK_ID_NATIVE, - 0, - vec![], - ); - - let entries = self.inputs.iter().filter_map(|Input { utxo_entry, .. }| utxo_entry.clone()).collect(); - SignableTransaction::with_entries(tx, entries) - } // todo use iterator instead of vector pub fn pass_signature_sync(mut self, sign_fn: SignFn) -> Result where @@ -220,6 +238,14 @@ impl PSKT { ); Ok(self) } + + pub fn calculate_id(&self) -> TransactionId { + self.calculate_id_internal() + } + + pub fn finalizer(self) -> PSKT { + PSKT { inner_pskt: self.inner_pskt, role: Default::default(), id: None } + } } pub struct SignInputOk { @@ -276,16 +302,77 @@ impl PSKT { self.finalize_internal(sigs) } + pub fn id(&self) -> Option { + self.id + } + + pub fn extractor(self) -> Result, TxNotFinalized> { + if self.id.is_none() { + Err(TxNotFinalized {}) + } else { + Ok(PSKT { inner_pskt: self.inner_pskt, role: Default::default(), id: self.id }) + } + } + fn finalize_internal(mut self, sigs: Result>, E>) -> Result> { let sigs = sigs?; if sigs.len() != self.inputs.len() { return Err(FinalizeError::WrongFinalizedSigsCount { expected: self.inputs.len(), actual: sigs.len() }); } - self.inner_pskt.inputs.iter_mut().zip(sigs).for_each(|(input, sig)| input.final_script_sig = Some(sig)); + self.inner_pskt.inputs.iter_mut().enumerate().zip(sigs).try_for_each(|((idx, input), sig)| { + if sig.is_empty() { + return Err(FinalizeError::EmptySignature(idx)); + } + input.sequence = Some(input.sequence.unwrap_or(u64::MAX)); // todo discussable + input.final_script_sig = Some(sig); + Ok(()) + })?; + self.id = Some(self.calculate_id_internal()); Ok(self) } } +impl PSKT { + pub fn determine_lock_time(&self) -> u64 { + self.inputs.iter().map(|input: &Input| input.min_time).max().unwrap_or(self.global.fallback_lock_time).unwrap_or(0) + } + + pub fn extract_tx_unchecked(self) -> impl FnOnce(u64) -> Transaction { + let lock_time = self.determine_lock_time(); + let mut tx = self.unsigned_tx().tx; + move |mass| { + tx.lock_time = lock_time; + tx.set_mass(mass); + tx + } + } + + pub fn extract_tx(mut self) -> Result Transaction, kaspa_txscript_errors::TxScriptError> { + let lock_time = self.determine_lock_time(); + let entries = + self.inner_pskt.inputs.iter_mut().filter_map(|Input { utxo_entry, .. }| utxo_entry.as_mut()).map(std::mem::take).collect(); + let tx = self.extract_tx_unchecked()(0); + + let tx = MutableTransaction::with_entries(tx, entries); + use kaspa_consensus_core::tx::VerifiableTransaction; + let tx = tx.as_verifiable(); + let cache = Cache::new(10_000); + let mut reused_values = SigHashReusedValues::new(); + + tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { + TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &mut reused_values, &cache)?.execute()?; + Ok(()) + })?; + let mut tx = tx.tx().clone(); + let closure = move |mass| { + tx.lock_time = lock_time; + tx.set_mass(mass); + tx + }; + Ok(closure) + } +} + /// Error combining pskt. #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] pub enum CombineError { @@ -301,10 +388,16 @@ pub enum CombineError { pub enum FinalizeError { #[error("Signatures count mismatch")] WrongFinalizedSigsCount { expected: usize, actual: usize }, + #[error("Signatures at index: {0} is empty")] + EmptySignature(usize), #[error(transparent)] FinalaziCb(#[from] E), } +#[derive(thiserror::Error, Debug, Clone)] +#[error("Transaction is not finalized")] +pub struct TxNotFinalized {} + #[cfg(test)] mod tests { diff --git a/wallet/pskt/src/role.rs b/wallet/pskt/src/role.rs index 1d777e627d..36be34a2d4 100644 --- a/wallet/pskt/src/role.rs +++ b/wallet/pskt/src/role.rs @@ -9,4 +9,4 @@ pub enum Combiner {} pub enum Finalizer {} -pub enum Extractor {} // todo use script engine to validate tx, determine lock time \ No newline at end of file +pub enum Extractor {} // todo use script engine to validate tx, determine lock time