From 67b2822e9e3985d4e64cf170da7032bdc4eb82d6 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:02:50 +0200 Subject: [PATCH] Process MuSig2 pubnonces and partial signatures in sign_tx rust-bitcoin's psbt module does not yet implement suport for PSBT_IN_MUSIG2_PUB_NONCE and PSBT_IN_MUSIG2_PARTIAL_SIG. This commit adds them to the PSBT using the raw 'unknown' map. It should be reverted once proper support is added upstream. --- src/ledger.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/ledger.rs b/src/ledger.rs index 9f5a286..456fa17 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -6,10 +6,10 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use async_trait::async_trait; use bitcoin::{ bip32::{DerivationPath, Fingerprint, Xpub}, - psbt::Psbt, + psbt::{raw, Psbt}, }; use ledger_bitcoin_client::psbt::PartialSignature; -use ledger_bitcoin_client::SignPsbtYieldedObject; +use ledger_bitcoin_client::{MusigPartialSignature, MusigPubNonce, SignPsbtYieldedObject}; use ledger_apdu::APDUAnswer; use ledger_transport_hidapi::TransportNativeHID; @@ -188,7 +188,17 @@ impl HWI for Ledger { input.tap_key_sig = Some(sig); } }, - _ => {} // Ignore MuSig2 and unknown payloads + SignPsbtYieldedObject::MusigPubNonce(nonce) => { + let key = musig2_pub_nonce_key(&nonce); + input.unknown.insert(key, nonce.pubnonce.to_vec()); + } + SignPsbtYieldedObject::MusigPartialSignature(partial) => { + let key = musig2_partial_sig_key(&partial); + input + .unknown + .insert(key, partial.partial_signature.to_vec()); + } + _ => {} // Ignore unknown payloads } } Ok(()) @@ -199,6 +209,38 @@ impl HWI for Ledger { } } +/// rust-bitcoin's PSBT implementation does not support musig2 fields, +/// so we encode them as unknown fields, until support is added. +/// BIP-373: PSBT_IN_MUSIG2_PUB_NONCE = 0x1b +/// Key: {0x1b}|{33-byte participant_pubkey}|{33-byte aggregate_pubkey}[|{32-byte tapleaf_hash}] +fn musig2_pub_nonce_key(nonce: &MusigPubNonce) -> raw::Key { + let mut key_data = Vec::with_capacity(66 + 32); + key_data.extend_from_slice(&nonce.participant_pubkey.to_bytes()); + key_data.extend_from_slice(&nonce.aggregate_pubkey.to_bytes()); + if let Some(tapleaf_hash) = nonce.tapleaf_hash { + key_data.extend_from_slice(tapleaf_hash.as_ref()); + } + raw::Key { + type_value: 0x1b, + key: key_data, + } +} + +/// BIP-373: PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c +/// Key: {0x1c}|{33-byte participant_pubkey}|{33-byte aggregate_pubkey}[|{32-byte tapleaf_hash}] +fn musig2_partial_sig_key(sig: &MusigPartialSignature) -> raw::Key { + let mut key_data = Vec::with_capacity(66 + 32); + key_data.extend_from_slice(&sig.participant_pubkey.to_bytes()); + key_data.extend_from_slice(&sig.aggregate_pubkey.to_bytes()); + if let Some(tapleaf_hash) = sig.tapleaf_hash { + key_data.extend_from_slice(tapleaf_hash.as_ref()); + } + raw::Key { + type_value: 0x1c, + key: key_data, + } +} + fn key_string_from_parts(fg: Fingerprint, path: DerivationPath, xpub: Xpub) -> String { format!( "[{}/{}]{}",