Skip to content

Commit 8f1f55a

Browse files
committed
psbt: Improved psbt::sign() support for single (non-xpub) secret keys
Can be provided as one key or an array of them, automatically translated into the pubkey->seckey map used for signing.
1 parent ee34bad commit 8f1f55a

File tree

3 files changed

+38
-6
lines changed

3 files changed

+38
-6
lines changed

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ pub enum RuntimeError {
305305
#[error("Missing fields to construct PSBT transaction output (amount and scriptPubKey/descriptor are required)")]
306306
PsbtTxOutMissingFields,
307307

308-
#[error("Invalid PSBT signing keys. Expected an Xpriv, array of Xprivs, or a tagged array mapping from single PubKeys to single SecKeys")]
308+
#[error("Invalid PSBT signing keys. Expected an Xpriv, array of Xprivs, single SecKey, array of single SecKeys, or a tagged array mapping from single PubKeys to single SecKeys")]
309309
PsbtInvalidSignKeys,
310310

311311
// Generic error raised from user-land Minsc code

src/runtime/array.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ impl Array {
2727
self.0
2828
}
2929

30+
pub fn into_iter_of<T: FromValue>(self) -> impl Iterator<Item = Result<T>> {
31+
self.into_iter().map(T::from_value)
32+
}
33+
3034
pub fn check_len(self, expected_len: usize) -> Result<Self> {
3135
ensure!(
3236
self.len() == expected_len,

src/stdlib/psbt.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use bitcoin::hex::DisplayHex;
77
use bitcoin::psbt::{self, raw, Psbt, SigningErrors, SigningKeys, SigningKeysMap};
88
use bitcoin::taproot::{self, LeafVersion, TapLeafHash};
99
use bitcoin::{hashes, secp256k1, PrivateKey, PublicKey, TxIn, TxOut, XOnlyPublicKey};
10+
use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey};
1011
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtOutputExt};
11-
use miniscript::DescriptorPublicKey;
1212

1313
use crate::runtime::{
1414
Array, Error, FieldAccess, FromValue, Mutable, Number::Int, Result, ScopeRef, Value,
@@ -202,17 +202,31 @@ fn sign_psbt(psbt: &mut Psbt, keys_val: Value) -> Result<(SigningKeysMap, Signin
202202
Value::Array(keys) if !keys.is_empty() => {
203203
// Peek at the first array element to determine its format
204204
match keys.first().expect("not empty") {
205-
// Keys provided as tagged array of [ $single_pk1:$single_sk1, $single_pk2:$single_sk2, ... ]
205+
// Keys provided as a map of [ $single_pk1:$single_sk1, $single_pk2:$single_sk2, ... ]
206206
Value::Array(_) => {
207207
psbt.sign(&BTreeMap::<PublicKey, PrivateKey>::try_from(keys)?, &EC)
208208
}
209+
209210
// Keys provided as [ $xpriv1, $xpriv2, ... ]
210-
Value::SecKey(_) => psbt.sign(&XprivSet(keys.try_into()?), &EC),
211+
Value::SecKey(DescriptorSecretKey::XPrv(_)) => {
212+
psbt.sign(&XprivSet(keys.try_into()?), &EC)
213+
}
214+
215+
// Keys provided as [ $single_sk1, $single_sk2, ... ]
216+
Value::SecKey(DescriptorSecretKey::Single(_)) => {
217+
psbt.sign(&single_seckeys_to_map(keys)?, &EC)
218+
}
211219
_ => bail!(Error::PsbtInvalidSignKeys),
212220
}
213221
}
214-
// Key provided as a single Xpriv
215-
Value::SecKey(_) => psbt.sign(&Xpriv::try_from(keys_val)?, &EC),
222+
// Key provided as one Xpriv
223+
Value::SecKey(DescriptorSecretKey::XPrv(_)) => psbt.sign(&Xpriv::try_from(keys_val)?, &EC),
224+
225+
// Key provided as one single key
226+
Value::SecKey(DescriptorSecretKey::Single(_)) => {
227+
psbt.sign(&single_seckey_to_map(PrivateKey::try_from(keys_val)?), &EC)
228+
}
229+
216230
// TODO support signing with MultiXpriv
217231
_ => bail!(Error::PsbtInvalidSignKeys),
218232
};
@@ -728,6 +742,20 @@ impl psbt::GetKey for XprivSet {
728742
}
729743
}
730744

745+
fn single_seckey_to_map(sk: PrivateKey) -> BTreeMap<PublicKey, PrivateKey> {
746+
let mut map = BTreeMap::new();
747+
map.insert(sk.public_key(&EC), sk);
748+
map
749+
}
750+
fn single_seckeys_to_map(keys: Array) -> Result<BTreeMap<PublicKey, PrivateKey>> {
751+
keys.into_iter_of()
752+
.map(|sk| {
753+
let sk: PrivateKey = sk?;
754+
Ok((sk.public_key(&EC), sk))
755+
})
756+
.collect()
757+
}
758+
731759
// PSBT fields accessors
732760
impl FieldAccess for Psbt {
733761
fn get_field(self, field: &Value) -> Option<Value> {

0 commit comments

Comments
 (0)