Skip to content
Draft
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
32 changes: 32 additions & 0 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ enum PlanOrPsbtInput {
/// Takes precedence over the sequence implied by `plan.relative_timelock`
/// when computing [`Input::sequence`].
sequence_override: Option<Sequence>,
sighash_type: Option<psbt::PsbtSighashType>,
},
PsbtInput {
psbt_input: Box<psbt::Input>,
Expand Down Expand Up @@ -153,6 +154,7 @@ impl PlanOrPsbtInput {
PlanOrPsbtInput::Plan {
plan,
sequence_override,
..
} => sequence_override.or_else(|| {
plan.relative_timelock
.map(|relative_timelock| relative_timelock.to_sequence())
Expand All @@ -161,6 +163,13 @@ impl PlanOrPsbtInput {
}
}

pub fn sighash_type(&self) -> Option<psbt::PsbtSighashType> {
match self {
PlanOrPsbtInput::Plan { sighash_type, .. } => *sighash_type,
PlanOrPsbtInput::PsbtInput { psbt_input, .. } => psbt_input.sighash_type,
}
}

pub fn satisfaction_weight(&self) -> usize {
match self {
PlanOrPsbtInput::Plan { plan, .. } => plan.satisfaction_weight(),
Expand Down Expand Up @@ -291,6 +300,7 @@ impl Input {
plan: PlanOrPsbtInput::Plan {
plan: Box::new(plan),
sequence_override: None,
sighash_type: None,
},
status,
is_coinbase,
Expand All @@ -312,6 +322,7 @@ impl Input {
plan: PlanOrPsbtInput::Plan {
plan: Box::new(plan),
sequence_override: None,
sighash_type: None,
},
status,
is_coinbase,
Expand Down Expand Up @@ -598,6 +609,26 @@ impl Input {
self.plan.sequence()
}

/// Sighash type.
pub fn sighash_type(&self) -> Option<psbt::PsbtSighashType> {
self.plan.sighash_type()
}

/// Set the sighash type for this input.
///
/// This overrides any sighash type already attached to the input — whether carried in via
/// [`Input::from_psbt_input`] (as [`psbt::Input::sighash_type`]) or set by a prior call.
///
/// Accepts anything convertible into [`psbt::PsbtSighashType`], including the standard
/// `EcdsaSighashType` and `TapSighashType` from `rust-bitcoin`.
pub fn set_sighash_type(&mut self, sighash_type: impl Into<psbt::PsbtSighashType>) {
let new = Some(sighash_type.into());
match &mut self.plan {
PlanOrPsbtInput::Plan { sighash_type, .. } => *sighash_type = new,
PlanOrPsbtInput::PsbtInput { psbt_input, .. } => psbt_input.sighash_type = new,
}
}
Comment thread
evanlinjin marked this conversation as resolved.

/// The weight in witness units needed for satisfying the [`Input`].
///
/// The satisfaction weight is the combined size of the fully satisfied input's witness
Expand Down Expand Up @@ -629,6 +660,7 @@ impl Input {
PlanOrPsbtInput::Plan {
plan,
sequence_override,
..
} => {
if let Some(required) = plan.absolute_timelock {
if sequence == Sequence::MAX {
Expand Down
51 changes: 41 additions & 10 deletions src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,6 @@ pub struct PsbtParams {
/// [`non_witness_utxo`]: bitcoin::psbt::Input::non_witness_utxo
pub mandate_full_tx_for_segwit_v0: bool,

/// Sighash type to be used for each input.
///
/// This option only applies to [`Input`]s that include a plan, as otherwise the given PSBT
/// input can be expected to set a specific sighash type. Defaults to `None` which will not
/// set an explicit sighash type for any input. (In that case the sighash will typically
/// cover all of the outputs).
pub sighash_type: Option<bitcoin::psbt::PsbtSighashType>,

/// Apply BIP-326 anti-fee-sniping (AFS) protection, using the given block height.
///
/// * `None` (default) — no AFS is applied.
Expand Down Expand Up @@ -84,7 +76,6 @@ impl Default for PsbtParams {
version: transaction::Version::TWO,
min_locktime: absolute::LockTime::ZERO,
mandate_full_tx_for_segwit_v0: true,
sighash_type: None,
anti_fee_sniping: None,
}
}
Expand Down Expand Up @@ -253,7 +244,7 @@ impl Selection {
}
}

psbt_input.sighash_type = params.sighash_type;
psbt_input.sighash_type = plan_input.sighash_type();

continue;
}
Expand Down Expand Up @@ -743,4 +734,44 @@ mod tests {
"should return UnsupportedVersion error for version < 2"
);
}

#[test]
fn test_set_sighash_type_propagates_to_psbt() -> anyhow::Result<()> {
use bitcoin::psbt::PsbtSighashType;
use bitcoin::TapSighashType;

let mut input = setup_test_input(2_000)?;

// Inputs start without a sighash type set.
assert_eq!(input.sighash_type(), None);

input.set_sighash_type(TapSighashType::Single);
assert_eq!(
input.sighash_type(),
Some(PsbtSighashType::from(TapSighashType::Single)),
);

// A subsequent set overrides the prior value.
input.set_sighash_type(TapSighashType::None);
assert_eq!(
input.sighash_type(),
Some(PsbtSighashType::from(TapSighashType::None)),
);

// `create_psbt` propagates the per-input sighash type into the PSBT input.
let selection = Selection {
inputs: vec![input],
outputs: vec![Output::with_script(
ScriptBuf::new(),
Amount::from_sat(9_000),
)],
};
let psbt = selection.create_psbt(PsbtParams::default())?;
assert_eq!(
psbt.inputs[0].sighash_type,
Some(PsbtSighashType::from(TapSighashType::None)),
);

Ok(())
}
}