Skip to content

Commit 1f43c4a

Browse files
authored
Merge pull request #142 from lightninglabs/signpsbt-multi
signpsbt: allow signing multiple inputs
2 parents 85f207c + 32fd44d commit 1f43c4a

File tree

1 file changed

+92
-63
lines changed

1 file changed

+92
-63
lines changed

cmd/chantools/signpsbt.go

Lines changed: 92 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/base64"
66
"encoding/binary"
7+
"errors"
78
"fmt"
89
"os"
910

@@ -15,6 +16,10 @@ import (
1516
"github.com/spf13/cobra"
1617
)
1718

19+
var (
20+
errNoPathFound = fmt.Errorf("no matching derivation path found")
21+
)
22+
1823
type signPSBTCommand struct {
1924
Psbt string
2025
FromRawPsbtFile string
@@ -134,93 +139,117 @@ func (c *signPSBTCommand) Execute(_ *cobra.Command, _ []string) error {
134139
func signPsbt(rootKey *hdkeychain.ExtendedKey,
135140
packet *psbt.Packet, signer *lnd.Signer) error {
136141

137-
// Check that we have an input with a derivation path that belongs to
138-
// the root key.
139-
derivationPath, inputIndex, err := findMatchingDerivationPath(
140-
rootKey, packet,
141-
)
142-
if err != nil {
143-
return fmt.Errorf("could not find matching derivation path: %w",
144-
err)
145-
}
142+
for inputIndex := range packet.Inputs {
143+
pIn := &packet.Inputs[inputIndex]
146144

147-
if len(derivationPath) < 5 {
148-
return fmt.Errorf("invalid derivation path, expected at least "+
149-
"5 elements, got %d", len(derivationPath))
150-
}
145+
// Check that we have an input with a derivation path that
146+
// belongs to the root key.
147+
derivationPath, err := findMatchingDerivationPath(rootKey, pIn)
148+
if errors.Is(err, errNoPathFound) {
149+
log.Infof("No matching derivation path found for "+
150+
"input %d, skipping", inputIndex)
151+
continue
152+
}
153+
if err != nil {
154+
return fmt.Errorf("could not find matching derivation "+
155+
"path: %w", err)
156+
}
151157

152-
localKey, err := lnd.DeriveChildren(rootKey, derivationPath)
153-
if err != nil {
154-
return fmt.Errorf("could not derive local key: %w", err)
155-
}
158+
if len(derivationPath) < 5 {
159+
return fmt.Errorf("invalid derivation path, expected "+
160+
"at least 5 elements, got %d",
161+
len(derivationPath))
162+
}
156163

157-
if packet.Inputs[inputIndex].WitnessUtxo == nil {
158-
return fmt.Errorf("invalid PSBT, input %d is missing witness "+
159-
"UTXO", inputIndex)
160-
}
161-
utxo := packet.Inputs[inputIndex].WitnessUtxo
162-
163-
// The signing is a bit different for P2WPKH, we need to specify the
164-
// pk script as the witness script.
165-
var witnessScript []byte
166-
if txscript.IsPayToWitnessPubKeyHash(utxo.PkScript) {
167-
witnessScript = utxo.PkScript
168-
} else {
169-
if len(packet.Inputs[inputIndex].WitnessScript) == 0 {
164+
localKey, err := lnd.DeriveChildren(rootKey, derivationPath)
165+
if err != nil {
166+
return fmt.Errorf("could not derive local key: %w", err)
167+
}
168+
169+
if pIn.WitnessUtxo == nil {
170170
return fmt.Errorf("invalid PSBT, input %d is missing "+
171-
"witness script", inputIndex)
171+
"witness UTXO", inputIndex)
172+
}
173+
utxo := pIn.WitnessUtxo
174+
175+
// The signing is a bit different for P2WPKH, we need to specify
176+
// the pk script as the witness script.
177+
var witnessScript []byte
178+
if txscript.IsPayToWitnessPubKeyHash(utxo.PkScript) {
179+
witnessScript = utxo.PkScript
180+
} else {
181+
if len(pIn.WitnessScript) == 0 {
182+
return fmt.Errorf("invalid PSBT, input %d is "+
183+
"missing witness script", inputIndex)
184+
}
185+
witnessScript = pIn.WitnessScript
172186
}
173-
witnessScript = packet.Inputs[inputIndex].WitnessScript
174-
}
175187

176-
localPrivateKey, err := localKey.ECPrivKey()
177-
if err != nil {
178-
return fmt.Errorf("error getting private key: %w", err)
179-
}
180-
err = signer.AddPartialSignatureForPrivateKey(
181-
packet, localPrivateKey, utxo, witnessScript, inputIndex,
182-
)
183-
if err != nil {
184-
return fmt.Errorf("error adding partial signature: %w", err)
188+
localPrivateKey, err := localKey.ECPrivKey()
189+
if err != nil {
190+
return fmt.Errorf("error getting private key: %w", err)
191+
}
192+
193+
// Do we already have a partial signature for our key?
194+
localPubKey := localPrivateKey.PubKey().SerializeCompressed()
195+
haveSig := false
196+
for _, partialSig := range pIn.PartialSigs {
197+
if bytes.Equal(partialSig.PubKey, localPubKey) {
198+
haveSig = true
199+
}
200+
}
201+
if haveSig {
202+
log.Infof("Already have a partial signature for input "+
203+
"%d and local key %x, skipping", inputIndex,
204+
localPubKey)
205+
continue
206+
}
207+
208+
err = signer.AddPartialSignatureForPrivateKey(
209+
packet, localPrivateKey, utxo, witnessScript,
210+
inputIndex,
211+
)
212+
if err != nil {
213+
return fmt.Errorf("error adding partial signature: %w",
214+
err)
215+
}
185216
}
186217

187218
return nil
188219
}
189220

190221
func findMatchingDerivationPath(rootKey *hdkeychain.ExtendedKey,
191-
packet *psbt.Packet) ([]uint32, int, error) {
222+
pIn *psbt.PInput) ([]uint32, error) {
192223

193224
pubKey, err := rootKey.ECPubKey()
194225
if err != nil {
195-
return nil, 0, fmt.Errorf("error getting public key: %w", err)
226+
return nil, fmt.Errorf("error getting public key: %w", err)
196227
}
197228

198229
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
199230
fingerprint := binary.LittleEndian.Uint32(pubKeyHash[:4])
200231

201-
for idx, input := range packet.Inputs {
202-
if len(input.Bip32Derivation) == 0 {
203-
continue
204-
}
232+
if len(pIn.Bip32Derivation) == 0 {
233+
return nil, errNoPathFound
234+
}
205235

206-
for _, derivation := range input.Bip32Derivation {
207-
// A special case where there is only a single
208-
// derivation path and the master key fingerprint is not
209-
// set, we assume we are the correct signer... This
210-
// might not be correct, but we have no way of knowing.
211-
if derivation.MasterKeyFingerprint == 0 &&
212-
len(input.Bip32Derivation) == 1 {
236+
for _, derivation := range pIn.Bip32Derivation {
237+
// A special case where there is only a single derivation path
238+
// and the master key fingerprint is not set, we assume we are
239+
// the correct signer... This might not be correct, but we have
240+
// no way of knowing.
241+
if derivation.MasterKeyFingerprint == 0 &&
242+
len(pIn.Bip32Derivation) == 1 {
213243

214-
return derivation.Bip32Path, idx, nil
215-
}
244+
return derivation.Bip32Path, nil
245+
}
216246

217-
// The normal case, where a derivation path has the
218-
// master fingerprint set.
219-
if derivation.MasterKeyFingerprint == fingerprint {
220-
return derivation.Bip32Path, idx, nil
221-
}
247+
// The normal case, where a derivation path has the master
248+
// fingerprint set.
249+
if derivation.MasterKeyFingerprint == fingerprint {
250+
return derivation.Bip32Path, nil
222251
}
223252
}
224253

225-
return nil, 0, fmt.Errorf("no matching derivation path found")
254+
return nil, errNoPathFound
226255
}

0 commit comments

Comments
 (0)