Skip to content

Commit 1ea3f12

Browse files
committed
psbt: Use the top-most Xpriv for signing, support multi-Xpriv
This is typically what the user wants, as the bip32_derivation and tap_key_origins PSBT fields usually point to the fingerprint of the top-most key and not to that of its child. This also adds support for multi-Xprivs, which now became trivial as all paths point to the same parent key.
1 parent 42bf435 commit 1ea3f12

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ pub enum RuntimeError {
9090
#[error("Expected a single Xpriv, not {0}")]
9191
NotSingleXpriv(Box<miniscript::descriptor::DescriptorSecretKey>),
9292

93+
#[error("Expected an Xpriv (single or multi-path), not {0}")]
94+
NotXpriv(Box<miniscript::descriptor::DescriptorSecretKey>),
95+
9396
#[error("Expected TapInfo or tr() Descriptor, not {}", ValErrFmt(.0))]
9497
NotTapInfoLike(Box<Value>),
9598

src/stdlib/keys.rs

+16
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,22 @@ impl TryFrom<Value> for Xpriv {
329329
}
330330
}
331331

332+
/// Type wrapper for converting into the top-most master Xpriv that the DescriptorSecretKey points to,
333+
/// without applying derivation steps like the Xpriv conversion (above). Also unlike it, this works
334+
/// with multi-Xprivs too since all paths point to the same parent key.
335+
#[derive(Debug, Clone)]
336+
pub struct MasterXpriv(pub Xpriv);
337+
impl TryFrom<Value> for MasterXpriv {
338+
type Error = Error;
339+
fn try_from(val: Value) -> Result<Self> {
340+
Ok(Self(match val.try_into()? {
341+
DescriptorSecretKey::XPrv(xprv) => xprv.xkey,
342+
DescriptorSecretKey::MultiXPrv(xprv) => xprv.xkey,
343+
sk @ DescriptorSecretKey::Single(_) => bail!(Error::NotXpriv(sk.into())),
344+
}))
345+
}
346+
}
347+
332348
// Convert from Value to BIP32 types
333349

334350
impl TryFrom<Value> for bip32::Fingerprint {

src/stdlib/psbt.rs

+28-9
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ use crate::display::{fmt_list, indentation_params, PrettyDisplay};
1414
use crate::runtime::{
1515
Array, Error, FieldAccess, FromValue, Mutable, Number::Int, Result, ScopeRef, Value,
1616
};
17-
use crate::stdlib::btc::WshScript;
1817
use crate::util::{DescriptorExt, DescriptorPubKeyExt, PsbtInExt, PsbtOutExt, TapInfoExt, EC};
1918

19+
use super::{btc::WshScript, keys::MasterXpriv};
20+
2021
pub fn attach_stdlib(scope: &ScopeRef<Mutable>) {
2122
let mut scope = scope.borrow_mut();
2223
scope.set_fn("psbt", fns::psbt).unwrap(); // create or update
@@ -201,8 +202,8 @@ fn sign_psbt(psbt: &mut Psbt, keys_val: Value) -> Result<(SigningKeysMap, Signin
201202
}
202203

203204
// Keys provided as [ $xpriv1, $xpriv2, ... ]
204-
Value::SecKey(DescriptorSecretKey::XPrv(_)) => {
205-
psbt.sign(&XprivSet(keys.try_into()?), &EC)
205+
Value::SecKey(DescriptorSecretKey::XPrv(_) | DescriptorSecretKey::MultiXPrv(_)) => {
206+
psbt.sign(&XprivSet::try_from(keys)?, &EC)
206207
}
207208

208209
// Keys provided as [ $single_sk1, $single_sk2, ... ]
@@ -212,15 +213,16 @@ fn sign_psbt(psbt: &mut Psbt, keys_val: Value) -> Result<(SigningKeysMap, Signin
212213
_ => bail!(Error::PsbtInvalidSignKeys),
213214
}
214215
}
215-
// Key provided as one Xpriv
216-
Value::SecKey(DescriptorSecretKey::XPrv(_)) => psbt.sign(&Xpriv::try_from(keys_val)?, &EC),
216+
// Key provided as an Xpriv
217+
Value::SecKey(DescriptorSecretKey::XPrv(_) | DescriptorSecretKey::MultiXPrv(_)) => {
218+
psbt.sign(&MasterXpriv::try_from(keys_val)?.0, &EC)
219+
}
217220

218-
// Key provided as one single key
221+
// Key provided as a single key
219222
Value::SecKey(DescriptorSecretKey::Single(_)) => {
220223
psbt.sign(&single_seckey_to_map(PrivateKey::try_from(keys_val)?), &EC)
221224
}
222225

223-
// TODO support signing with MultiXpriv
224226
_ => bail!(Error::PsbtInvalidSignKeys),
225227
};
226228
// Returns input signing failures in the the Ok variant. An Err is only raised if the `seckeys` argument is invalid.
@@ -736,9 +738,26 @@ impl TryFrom<Value> for raw::ProprietaryKey {
736738
}
737739
}
738740

739-
// GetKey wrapper around Vec<Xpriv>, needed as a workaround for https://github.com/rust-bitcoin/rust-bitcoin/pull/2850
740-
// Could otherwise directly use `psbt.sign(&Vec::<Xpriv>::try_from(seckeys)?, &EC)`
741+
// GetKey wrapper around Vec<Xpriv>
741742
struct XprivSet(Vec<Xpriv>);
743+
744+
// Convert an Array into a set of Xprivs used for PSBT signing.
745+
// Unlike the the standard TryInto<Xpriv> conversion which derives the final child Xpriv after applying all derivation
746+
// steps, this instead uses the top-most known Xpriv without deriving. The PSBT bip32_derivation/tap_key_origins fields
747+
// are expected to point to the fingerprint of the top-most key, and not to that of the child.
748+
impl TryFrom<Array> for XprivSet {
749+
type Error = Error;
750+
fn try_from(arr: Array) -> Result<Self> {
751+
Ok(Self(
752+
arr.into_iter_of()
753+
.map(|xprv: Result<MasterXpriv>| Ok(xprv?.0))
754+
.collect::<Result<Vec<Xpriv>>>()?,
755+
))
756+
}
757+
}
758+
759+
// Needed as a workaround for https://github.com/rust-bitcoin/rust-bitcoin/pull/2850
760+
// Could otherwise use Psbt::sign() with the inner Vec<Xpriv>.
742761
impl psbt::GetKey for XprivSet {
743762
type Error = <Xpriv as psbt::GetKey>::Error;
744763

0 commit comments

Comments
 (0)