Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions crates/sdk/src/signer/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use simplicityhl::Value;
use simplicityhl::WitnessValues;
use simplicityhl::elements::pset::PartiallySignedTransaction;
use simplicityhl::elements::secp256k1_zkp::{All, Keypair, Message, Secp256k1, ecdsa, schnorr};
use simplicityhl::elements::{Address, Script, Transaction};
use simplicityhl::elements::{Address, LockTime, Script, Sequence, Transaction};
#[cfg(feature = "provider")]
use simplicityhl::elements::{AssetId, OutPoint, Txid};
use simplicityhl::simplicity::bitcoin::XOnlyPublicKey;
Expand Down Expand Up @@ -602,7 +602,14 @@ impl Signer {
let pruned_witness = program_input
.program
.finalize(&pst, &signed_witness.unwrap(), index, &self.network)
.map_err(|source| SignerError::CovenantExecution { index, source })?;
.map_err(|source| SignerError::CovenantExecution {
index,
locktime: pst.locktime().map_or(0, LockTime::to_consensus_u32),
sequence: pst.inputs()[index]
.sequence
.map_or(u32::MAX, Sequence::to_consensus_u32),
source,
})?;

pst.inputs_mut()[index].final_script_witness = Some(pruned_witness);
} else {
Expand Down
8 changes: 7 additions & 1 deletion crates/sdk/src/signer/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ pub enum SignerError {
Program(#[from] ProgramError),

/// Error indicating that a Simplicity program failed to satisfy, prune or execute.
#[error("Covenant input {index} did not execute: {source}")]
#[error(
"Covenant input {index} did not execute (transaction locktime {locktime}, input sequence {sequence}): {source}"
)]
CovenantExecution {
/// The index of the input whose program failed.
index: usize,
/// The locktime the transaction being satisfied carries.
locktime: u32,
/// The sequence of the failing input.
sequence: u32,
/// The underlying program failure.
source: ProgramError,
},
Expand Down
91 changes: 82 additions & 9 deletions crates/sdk/src/transaction/final_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bitcoin_hashes::sha256;

use simplicityhl::elements::pset::{Input, PartiallySignedTransaction};
use simplicityhl::elements::{
AssetId, TxOutSecrets,
AssetId, LockTime, Sequence, TxOutSecrets,
confidential::{AssetBlindingFactor, ValueBlindingFactor},
};

Expand Down Expand Up @@ -108,8 +108,8 @@ impl FinalInput {
/// # Panics
///
/// This function will panic if the `issuance_input` is of type `Reissuance`
/// and the `partial_input.secrets` field is `None` or does not contain the necessary
/// confidential information. Specifically, a panic occurs when attempting to unwrap the `asset_bf` value.
/// and the `partial_input.secrets` field is `None` or does not contain the necessary
/// confidential information. Specifically, a panic occurs when attempting to unwrap the `asset_bf` value.
#[must_use]
pub fn to_input(&self) -> Input {
let mut pst_input = self.partial_input.to_input();
Expand Down Expand Up @@ -145,6 +145,8 @@ pub struct FinalTransaction {
inputs: Vec<FinalInput>,
outputs: Vec<PartialOutput>,
change: Option<ChangeOutput>,
sequence: Sequence,
locktime: LockTime,
}

impl FinalTransaction {
Expand All @@ -156,8 +158,23 @@ impl FinalTransaction {
inputs: Vec::new(),
outputs: Vec::new(),
change: None,
sequence: Sequence::default(),
locktime: LockTime::ZERO,
}
}
/// Sets a specific `Sequence` for the transaction.
///
/// Injects this value into the inputs that don't declare their own sequence.
pub fn set_sequence(&mut self, sequence: Sequence) {
self.sequence = sequence;
}

/// Sets a specific `LockTime` for the transaction.
///
/// Injects this value into the inputs that don't declare their own locktime.
pub fn set_locktime(&mut self, locktime: LockTime) {
self.locktime = locktime;
}

/// Sets where this transaction's change should go.
///
Expand Down Expand Up @@ -347,13 +364,13 @@ impl FinalTransaction {

for input in &self.inputs {
match input.partial_input.secrets {
// this is an unblinded confidential input
// This is an unblinded confidential input
Some(secrets) => {
if secrets.asset == network.policy_asset() {
available_amount += secrets.value;
}
}
// this is an explicit input
// This is an explicit input
None => {
if input.partial_input.asset.unwrap() == network.policy_asset() {
available_amount += input.partial_input.amount.unwrap();
Expand Down Expand Up @@ -401,13 +418,24 @@ impl FinalTransaction {
let mut pst = PartiallySignedTransaction::new_v2();

for i in 0..self.inputs.len() {
let final_input = &self.inputs[i];
let mut final_input = self.inputs[i].clone();

// Inject sequence if the input has none
if final_input.partial_input.sequence == Sequence::default() {
final_input.partial_input = final_input.partial_input.with_sequence(self.sequence);
}

// Inject locktime if the input has none
if final_input.partial_input.locktime == LockTime::ZERO {
final_input.partial_input = final_input.partial_input.with_locktime(self.locktime);
}

let pst_input = final_input.to_input();

match final_input.partial_input.secrets {
// insert input secrets if present
// Insert input secrets if present
Some(secrets) => input_secrets.insert(i, secrets),
// else populate input secrets with "explicit" amounts
// Else populate input secrets with "explicit" amounts
None => input_secrets.insert(
i,
TxOutSecrets {
Expand Down Expand Up @@ -442,7 +470,7 @@ impl FinalTransaction {
mod tests {
use bitcoin_hashes::Hash;

use simplicityhl::elements::{OutPoint, Script, TxOut, Txid};
use simplicityhl::elements::{LockTime, OutPoint, Script, TxOut, Txid};

use crate::transaction::UTXO;

Expand Down Expand Up @@ -505,6 +533,51 @@ mod tests {
assert_eq!(secrets, expected_secrets);
}

#[test]
fn declared_height_becomes_the_transactions_locktime() {
let policy = dummy_asset_id(0xAA);
let mut ft = FinalTransaction::new();

ft.add_input(
PartialInput::new(explicit_utxo(0x01, 0, 5000, policy)),
RequiredSignature::None,
);
ft.add_input(
PartialInput::new(explicit_utxo(0x02, 0, 5000, policy)),
RequiredSignature::None,
);
ft.add_output(PartialOutput::new(Script::new(), 9000, policy));
ft.set_locktime(LockTime::from_height(2_580_990).unwrap());

let (pst, _) = ft.extract_pst();

assert_eq!(
pst.locktime().expect("one height, so no conflict"),
LockTime::from_height(2_580_990).unwrap()
);
assert!(
pst.inputs()
.iter()
.all(|input| input.required_height_locktime.is_some())
);
}

#[test]
fn transaction_that_declares_no_height_still_has_none() {
let policy = dummy_asset_id(0xAA);
let mut ft = FinalTransaction::new();

ft.add_input(
PartialInput::new(explicit_utxo(0x01, 0, 5000, policy)),
RequiredSignature::None,
);
ft.add_output(PartialOutput::new(Script::new(), 4000, policy));

let (pst, _) = ft.extract_pst();

assert_eq!(pst.locktime().unwrap(), LockTime::ZERO);
}

#[test]
fn extract_pst_single_confidential_input() {
let policy = dummy_asset_id(0xAA);
Expand Down
5 changes: 2 additions & 3 deletions crates/sdk/src/transaction/partial_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,9 @@ impl PartialInput {
LockTime::Seconds(value) => Some(value),
LockTime::Blocks(_) => None,
};
// zero height locktime is essentially ignored
let height_locktime = match self.locktime {
LockTime::Blocks(value) => Some(value),
LockTime::Seconds(_) => None,
LockTime::Blocks(value) if value.to_consensus_u32() > 0 => Some(value),
LockTime::Blocks(_) | LockTime::Seconds(_) => None,
};

Input {
Expand Down
Loading