• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lnwallet/rpcwallet/rpcwallet.go
1
package rpcwallet
2

3
import (
4
        "bytes"
5
        "context"
6
        "crypto/sha256"
7
        "crypto/x509"
8
        "errors"
9
        "fmt"
10
        "os"
11
        "time"
12

13
        "github.com/btcsuite/btcd/btcec/v2"
14
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
15
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
16
        "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
17
        "github.com/btcsuite/btcd/btcutil"
18
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
19
        "github.com/btcsuite/btcd/btcutil/psbt"
20
        "github.com/btcsuite/btcd/chaincfg"
21
        "github.com/btcsuite/btcd/txscript"
22
        "github.com/btcsuite/btcd/wire"
23
        "github.com/btcsuite/btcwallet/waddrmgr"
24
        basewallet "github.com/btcsuite/btcwallet/wallet"
25
        "github.com/lightningnetwork/lnd/fn/v2"
26
        "github.com/lightningnetwork/lnd/input"
27
        "github.com/lightningnetwork/lnd/keychain"
28
        "github.com/lightningnetwork/lnd/lncfg"
29
        "github.com/lightningnetwork/lnd/lnrpc/signrpc"
30
        "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
31
        "github.com/lightningnetwork/lnd/lnwallet"
32
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
33
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
34
        "github.com/lightningnetwork/lnd/lnwire"
35
        "github.com/lightningnetwork/lnd/macaroons"
36
        "google.golang.org/grpc"
37
        "google.golang.org/grpc/codes"
38
        "google.golang.org/grpc/credentials"
39
        "google.golang.org/grpc/status"
40
        "gopkg.in/macaroon.v2"
41
)
42

43
var (
44
        // ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned
45
        // if an operation is requested from the RPC wallet that is not
46
        // supported in remote signing mode.
47
        ErrRemoteSigningPrivateKeyNotAvailable = errors.New("deriving " +
48
                "private key is not supported by RPC based key ring")
49
)
50

51
// RPCKeyRing is an implementation of the SecretKeyRing interface that uses a
52
// local watch-only wallet for keeping track of addresses and transactions but
53
// delegates any signing or ECDH operations to a remote node through RPC.
54
type RPCKeyRing struct {
55
        // WalletController is the embedded wallet controller of the watch-only
56
        // base wallet. We need to overwrite/shadow certain of the implemented
57
        // methods to make sure we can mirror them to the remote wallet.
58
        lnwallet.WalletController
59

60
        watchOnlyKeyRing keychain.SecretKeyRing
61

62
        netParams *chaincfg.Params
63

64
        rpcTimeout time.Duration
65

66
        signerClient signrpc.SignerClient
67
        walletClient walletrpc.WalletKitClient
68
}
69

70
var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil)
71
var _ input.Signer = (*RPCKeyRing)(nil)
72
var _ keychain.MessageSignerRing = (*RPCKeyRing)(nil)
73
var _ lnwallet.WalletController = (*RPCKeyRing)(nil)
74

75
// NewRPCKeyRing creates a new remote signing secret key ring that uses the
76
// given watch-only base wallet to keep track of addresses and transactions but
77
// delegates any signing or ECDH operations to the remove signer through RPC.
78
func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing,
79
        watchOnlyWalletController lnwallet.WalletController,
80
        remoteSigner *lncfg.RemoteSigner,
UNCOV
81
        netParams *chaincfg.Params) (*RPCKeyRing, error) {
×
UNCOV
82

×
UNCOV
83
        rpcConn, err := connectRPC(
×
UNCOV
84
                remoteSigner.RPCHost, remoteSigner.TLSCertPath,
×
UNCOV
85
                remoteSigner.MacaroonPath, remoteSigner.Timeout,
×
UNCOV
86
        )
×
UNCOV
87
        if err != nil {
×
88
                return nil, fmt.Errorf("error connecting to the remote "+
×
89
                        "signing node through RPC: %v", err)
×
90
        }
×
91

UNCOV
92
        return &RPCKeyRing{
×
UNCOV
93
                WalletController: watchOnlyWalletController,
×
UNCOV
94
                watchOnlyKeyRing: watchOnlyKeyRing,
×
UNCOV
95
                netParams:        netParams,
×
UNCOV
96
                rpcTimeout:       remoteSigner.Timeout,
×
UNCOV
97
                signerClient:     signrpc.NewSignerClient(rpcConn),
×
UNCOV
98
                walletClient:     walletrpc.NewWalletKitClient(rpcConn),
×
UNCOV
99
        }, nil
×
100
}
101

102
// NewAddress returns the next external or internal address for the
103
// wallet dictated by the value of the `change` parameter. If change is
104
// true, then an internal address should be used, otherwise an external
105
// address should be returned. The type of address returned is dictated
106
// by the wallet's capabilities, and may be of type: p2sh, p2wkh,
107
// p2wsh, etc. The account parameter must be non-empty as it determines
108
// which account the address should be generated from.
109
func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
UNCOV
110
        account string) (btcutil.Address, error) {
×
UNCOV
111

×
UNCOV
112
        return r.WalletController.NewAddress(addrType, change, account)
×
UNCOV
113
}
×
114

115
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
116
// the specified outputs. In the case the wallet has insufficient funds, or the
117
// outputs are non-standard, a non-nil error will be returned.
118
//
119
// NOTE: This method requires the global coin selection lock to be held.
120
//
121
// NOTE: This is a part of the WalletController interface.
122
//
123
// NOTE: This method only signs with BIP49/84 keys.
124
func (r *RPCKeyRing) SendOutputs(inputs fn.Set[wire.OutPoint],
125
        outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
126
        minConfs int32, label string,
UNCOV
127
        strategy basewallet.CoinSelectionStrategy) (*wire.MsgTx, error) {
×
UNCOV
128

×
UNCOV
129
        tx, err := r.WalletController.SendOutputs(
×
UNCOV
130
                inputs, outputs, feeRate, minConfs, label, strategy,
×
UNCOV
131
        )
×
UNCOV
132
        if err != nil && err != basewallet.ErrTxUnsigned {
×
133
                return nil, err
×
134
        }
×
UNCOV
135
        if err == nil {
×
136
                // This shouldn't happen since our wallet controller is watch-
×
137
                // only and can't sign the TX.
×
138
                return tx, nil
×
139
        }
×
140

141
        // We know at this point that we only have inputs from our own wallet.
142
        // So we can just compute the input script using the remote signer.
UNCOV
143
        outputFetcher := lnwallet.NewWalletPrevOutputFetcher(r.WalletController)
×
UNCOV
144
        for i, txIn := range tx.TxIn {
×
UNCOV
145
                signDesc := input.SignDescriptor{
×
UNCOV
146
                        HashType: txscript.SigHashAll,
×
UNCOV
147
                        SigHashes: txscript.NewTxSigHashes(
×
UNCOV
148
                                tx, outputFetcher,
×
UNCOV
149
                        ),
×
UNCOV
150
                        PrevOutputFetcher: outputFetcher,
×
UNCOV
151
                }
×
UNCOV
152

×
UNCOV
153
                // We can only sign this input if it's ours, so we'll ask the
×
UNCOV
154
                // watch-only wallet if it can map this outpoint into a coin we
×
UNCOV
155
                // own. If not, then we can't continue because our wallet state
×
UNCOV
156
                // is out of sync.
×
UNCOV
157
                info, err := r.WalletController.FetchOutpointInfo(
×
UNCOV
158
                        &txIn.PreviousOutPoint,
×
UNCOV
159
                )
×
UNCOV
160
                if err != nil {
×
161
                        return nil, fmt.Errorf("error looking up utxo: %w", err)
×
162
                }
×
163

UNCOV
164
                if txscript.IsPayToTaproot(info.PkScript) {
×
UNCOV
165
                        signDesc.HashType = txscript.SigHashDefault
×
UNCOV
166
                }
×
167

168
                // Now that we know the input is ours, we'll populate the
169
                // signDesc with the per input unique information.
UNCOV
170
                signDesc.Output = &wire.TxOut{
×
UNCOV
171
                        Value:    int64(info.Value),
×
UNCOV
172
                        PkScript: info.PkScript,
×
UNCOV
173
                }
×
UNCOV
174
                signDesc.InputIndex = i
×
UNCOV
175

×
UNCOV
176
                // Finally, we'll sign the input as is, and populate the input
×
UNCOV
177
                // with the witness and sigScript (if needed).
×
UNCOV
178
                inputScript, err := r.ComputeInputScript(tx, &signDesc)
×
UNCOV
179
                if err != nil {
×
180
                        return nil, err
×
181
                }
×
182

UNCOV
183
                txIn.SignatureScript = inputScript.SigScript
×
UNCOV
184
                txIn.Witness = inputScript.Witness
×
185
        }
186

UNCOV
187
        return tx, r.WalletController.PublishTransaction(tx, label)
×
188
}
189

190
// SignPsbt expects a partial transaction with all inputs and outputs fully
191
// declared and tries to sign all unsigned inputs that have all required fields
192
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
193
// If no error is returned, the PSBT is ready to be given to the next signer or
194
// to be finalized if lnd was the last signer.
195
//
196
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
197
// perform any other tasks (such as coin selection, UTXO locking or
198
// input/output/fee value validation, PSBT finalization). Any input that is
199
// incomplete will be skipped.
UNCOV
200
func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
×
UNCOV
201
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
202
        defer cancel()
×
UNCOV
203

×
UNCOV
204
        var buf bytes.Buffer
×
UNCOV
205
        if err := packet.Serialize(&buf); err != nil {
×
206
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
207
        }
×
208

UNCOV
209
        resp, err := r.walletClient.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{
×
UNCOV
210
                FundedPsbt: buf.Bytes(),
×
UNCOV
211
        })
×
UNCOV
212
        if err != nil {
×
213
                considerShutdown(err)
×
214
                return nil, fmt.Errorf("error signing PSBT in remote signer "+
×
215
                        "instance: %v", err)
×
216
        }
×
217

UNCOV
218
        signedPacket, err := psbt.NewFromRawBytes(
×
UNCOV
219
                bytes.NewReader(resp.SignedPsbt), false,
×
UNCOV
220
        )
×
UNCOV
221
        if err != nil {
×
222
                return nil, fmt.Errorf("error parsing signed PSBT: %w", err)
×
223
        }
×
224

225
        // The caller expects the packet to be modified instead of a new
226
        // instance to be returned. So we just overwrite all fields in the
227
        // original packet.
UNCOV
228
        packet.UnsignedTx = signedPacket.UnsignedTx
×
UNCOV
229
        packet.Inputs = signedPacket.Inputs
×
UNCOV
230
        packet.Outputs = signedPacket.Outputs
×
UNCOV
231
        packet.Unknowns = signedPacket.Unknowns
×
UNCOV
232

×
UNCOV
233
        return resp.SignedInputs, nil
×
234
}
235

236
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
237
// declared and tries to sign all inputs that belong to the specified account.
238
// Lnd must be the last signer of the transaction. That means, if there are any
239
// unsigned non-witness inputs or inputs without UTXO information attached or
240
// inputs without witness data that do not belong to lnd's wallet, this method
241
// will fail. If no error is returned, the PSBT is ready to be extracted and the
242
// final TX within to be broadcast.
243
//
244
// NOTE: This method does NOT publish the transaction after it's been
245
// finalized successfully.
246
//
247
// NOTE: This is a part of the WalletController interface.
248
//
249
// NOTE: We need to overwrite this method because we need to redirect the call
250
// to ComputeInputScript to the RPC key ring's implementation. If we forward
251
// the call to the default WalletController implementation, we get an error
252
// since that wallet is watch-only. If we forward the call to the remote signer,
253
// we get an error because the signer doesn't know the UTXO information required
254
// in ComputeInputScript.
255
//
256
// TODO(guggero): Refactor btcwallet to accept ComputeInputScript as a function
257
// parameter in FinalizePsbt so we can get rid of this code duplication.
UNCOV
258
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error {
×
UNCOV
259
        // Let's check that this is actually something we can and want to sign.
×
UNCOV
260
        // We need at least one input and one output. In addition each
×
UNCOV
261
        // input needs nonWitness Utxo or witness Utxo data specified.
×
UNCOV
262
        err := psbt.InputsReadyToSign(packet)
×
UNCOV
263
        if err != nil {
×
264
                return err
×
265
        }
×
266

267
        // Go through each input that doesn't have final witness data attached
268
        // to it already and try to sign it. We do expect that we're the last
269
        // ones to sign. If there is any input without witness data that we
270
        // cannot sign because it's not our UTXO, this will be a hard failure.
UNCOV
271
        tx := packet.UnsignedTx
×
UNCOV
272
        prevOutFetcher := basewallet.PsbtPrevOutputFetcher(packet)
×
UNCOV
273
        sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher)
×
UNCOV
274
        for idx, txIn := range tx.TxIn {
×
UNCOV
275
                in := packet.Inputs[idx]
×
UNCOV
276

×
UNCOV
277
                // We can only sign if we have UTXO information available. We
×
UNCOV
278
                // can just continue here as a later step will fail with a more
×
UNCOV
279
                // precise error message.
×
UNCOV
280
                if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
×
281
                        continue
×
282
                }
283

284
                // Skip this input if it's got final witness data attached.
UNCOV
285
                if len(in.FinalScriptWitness) > 0 {
×
286
                        continue
×
287
                }
288

289
                // We can only sign this input if it's ours, so we try to map it
290
                // to a coin we own. If we can't, then we'll continue as it
291
                // isn't our input.
UNCOV
292
                utxo, err := r.FetchOutpointInfo(&txIn.PreviousOutPoint)
×
UNCOV
293
                if err != nil {
×
294
                        continue
×
295
                }
UNCOV
296
                fullTx := utxo.PrevTx
×
UNCOV
297
                signDesc := &input.SignDescriptor{
×
UNCOV
298
                        KeyDesc: keychain.KeyDescriptor{},
×
UNCOV
299
                        Output: &wire.TxOut{
×
UNCOV
300
                                Value:    int64(utxo.Value),
×
UNCOV
301
                                PkScript: utxo.PkScript,
×
UNCOV
302
                        },
×
UNCOV
303
                        HashType:          in.SighashType,
×
UNCOV
304
                        SigHashes:         sigHashes,
×
UNCOV
305
                        InputIndex:        idx,
×
UNCOV
306
                        PrevOutputFetcher: prevOutFetcher,
×
UNCOV
307
                }
×
UNCOV
308

×
UNCOV
309
                // Find out what UTXO we are signing. Wallets _should_ always
×
UNCOV
310
                // provide the full non-witness UTXO for segwit v0.
×
UNCOV
311
                var signOutput *wire.TxOut
×
UNCOV
312
                if in.NonWitnessUtxo != nil {
×
UNCOV
313
                        prevIndex := txIn.PreviousOutPoint.Index
×
UNCOV
314
                        signOutput = in.NonWitnessUtxo.TxOut[prevIndex]
×
UNCOV
315

×
UNCOV
316
                        if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
×
317
                                return fmt.Errorf("found UTXO %#v but it "+
×
318
                                        "doesn't match PSBT's input %v",
×
319
                                        signDesc.Output, signOutput)
×
320
                        }
×
321

UNCOV
322
                        if fullTx.TxHash() != txIn.PreviousOutPoint.Hash {
×
323
                                return fmt.Errorf("found UTXO tx %v but it "+
×
324
                                        "doesn't match PSBT's input %v",
×
325
                                        fullTx.TxHash(),
×
326
                                        txIn.PreviousOutPoint.Hash)
×
327
                        }
×
328
                }
329

330
                // Fall back to witness UTXO only for older wallets.
UNCOV
331
                if in.WitnessUtxo != nil {
×
UNCOV
332
                        signOutput = in.WitnessUtxo
×
UNCOV
333

×
UNCOV
334
                        if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
×
335
                                return fmt.Errorf("found UTXO %#v but it "+
×
336
                                        "doesn't match PSBT's input %v",
×
337
                                        signDesc.Output, signOutput)
×
338
                        }
×
339
                }
340

341
                // Do the actual signing in ComputeInputScript which in turn
342
                // will invoke the remote signer.
UNCOV
343
                script, err := r.ComputeInputScript(tx, signDesc)
×
UNCOV
344
                if err != nil {
×
345
                        return fmt.Errorf("error computing input script for "+
×
346
                                "input %d: %v", idx, err)
×
347
                }
×
348

349
                // Serialize the witness format from the stack representation to
350
                // the wire representation.
UNCOV
351
                var witnessBytes bytes.Buffer
×
UNCOV
352
                err = psbt.WriteTxWitness(&witnessBytes, script.Witness)
×
UNCOV
353
                if err != nil {
×
354
                        return fmt.Errorf("error serializing witness: %w", err)
×
355
                }
×
UNCOV
356
                packet.Inputs[idx].FinalScriptWitness = witnessBytes.Bytes()
×
UNCOV
357
                packet.Inputs[idx].FinalScriptSig = script.SigScript
×
358
        }
359

360
        // Make sure the PSBT itself thinks it's finalized and ready to be
361
        // broadcast.
UNCOV
362
        err = psbt.MaybeFinalizeAll(packet)
×
UNCOV
363
        if err != nil {
×
364
                return fmt.Errorf("error finalizing PSBT: %w", err)
×
365
        }
×
366

UNCOV
367
        return nil
×
368
}
369

370
// DeriveNextKey attempts to derive the *next* key within the key family
371
// (account in BIP43) specified. This method should return the next external
372
// child within this branch.
373
//
374
// NOTE: This method is part of the keychain.KeyRing interface.
375
func (r *RPCKeyRing) DeriveNextKey(
UNCOV
376
        keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
×
UNCOV
377

×
UNCOV
378
        return r.watchOnlyKeyRing.DeriveNextKey(keyFam)
×
UNCOV
379
}
×
380

381
// DeriveKey attempts to derive an arbitrary key specified by the passed
382
// KeyLocator. This may be used in several recovery scenarios, or when manually
383
// rotating something like our current default node key.
384
//
385
// NOTE: This method is part of the keychain.KeyRing interface.
386
func (r *RPCKeyRing) DeriveKey(
UNCOV
387
        keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
×
UNCOV
388

×
UNCOV
389
        return r.watchOnlyKeyRing.DeriveKey(keyLoc)
×
UNCOV
390
}
×
391

392
// ECDH performs a scalar multiplication (ECDH-like operation) between the
393
// target key descriptor and remote public key. The output returned will be the
394
// sha256 of the resulting shared point serialized in compressed format. If k is
395
// our private key, and P is the public key, we perform the following operation:
396
//
397
//        sx := k*P
398
//        s := sha256(sx.SerializeCompressed())
399
//
400
// NOTE: This method is part of the keychain.ECDHRing interface.
401
func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
UNCOV
402
        pubKey *btcec.PublicKey) ([32]byte, error) {
×
UNCOV
403

×
UNCOV
404
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
405
        defer cancel()
×
UNCOV
406

×
UNCOV
407
        key := [32]byte{}
×
UNCOV
408
        req := &signrpc.SharedKeyRequest{
×
UNCOV
409
                EphemeralPubkey: pubKey.SerializeCompressed(),
×
UNCOV
410
                KeyDesc: &signrpc.KeyDescriptor{
×
UNCOV
411
                        KeyLoc: &signrpc.KeyLocator{
×
UNCOV
412
                                KeyFamily: int32(keyDesc.Family),
×
UNCOV
413
                                KeyIndex:  int32(keyDesc.Index),
×
UNCOV
414
                        },
×
UNCOV
415
                },
×
UNCOV
416
        }
×
UNCOV
417

×
UNCOV
418
        if keyDesc.Index == 0 && keyDesc.PubKey != nil {
×
UNCOV
419
                req.KeyDesc.RawKeyBytes = keyDesc.PubKey.SerializeCompressed()
×
UNCOV
420
        }
×
421

UNCOV
422
        resp, err := r.signerClient.DeriveSharedKey(ctxt, req)
×
UNCOV
423
        if err != nil {
×
424
                considerShutdown(err)
×
425
                return key, fmt.Errorf("error deriving shared key in remote "+
×
426
                        "signer instance: %v", err)
×
427
        }
×
428

UNCOV
429
        copy(key[:], resp.SharedKey)
×
UNCOV
430
        return key, nil
×
431
}
432

433
// SignMessage attempts to sign a target message with the private key described
434
// in the key locator. If the target private key is unable to be found, then an
435
// error will be returned. The actual digest signed is the single or double
436
// SHA-256 of the passed message.
437
//
438
// NOTE: This method is part of the keychain.MessageSignerRing interface.
439
func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator,
UNCOV
440
        msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
×
UNCOV
441

×
UNCOV
442
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
443
        defer cancel()
×
UNCOV
444

×
UNCOV
445
        resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
×
UNCOV
446
                Msg: msg,
×
UNCOV
447
                KeyLoc: &signrpc.KeyLocator{
×
UNCOV
448
                        KeyFamily: int32(keyLoc.Family),
×
UNCOV
449
                        KeyIndex:  int32(keyLoc.Index),
×
UNCOV
450
                },
×
UNCOV
451
                DoubleHash: doubleHash,
×
UNCOV
452
        })
×
UNCOV
453
        if err != nil {
×
454
                considerShutdown(err)
×
455
                return nil, fmt.Errorf("error signing message in remote "+
×
456
                        "signer instance: %v", err)
×
457
        }
×
458

UNCOV
459
        wireSig, err := lnwire.NewSigFromECDSARawSignature(resp.Signature)
×
UNCOV
460
        if err != nil {
×
461
                return nil, fmt.Errorf("unable to create sig: %w", err)
×
462
        }
×
UNCOV
463
        sig, err := wireSig.ToSignature()
×
UNCOV
464
        if err != nil {
×
465
                return nil, fmt.Errorf("unable to parse sig: %w", err)
×
466
        }
×
UNCOV
467
        ecdsaSig, ok := sig.(*ecdsa.Signature)
×
UNCOV
468
        if !ok {
×
469
                return nil, fmt.Errorf("unexpected signature type: %T", sig)
×
470
        }
×
471

UNCOV
472
        return ecdsaSig, nil
×
473
}
474

475
// SignMessageCompact signs the given message, single or double SHA256 hashing
476
// it first, with the private key described in the key locator and returns the
477
// signature in the compact, public key recoverable format.
478
//
479
// NOTE: This method is part of the keychain.MessageSignerRing interface.
480
func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator,
UNCOV
481
        msg []byte, doubleHash bool) ([]byte, error) {
×
UNCOV
482

×
UNCOV
483
        if keyLoc.Family != keychain.KeyFamilyNodeKey {
×
484
                return nil, fmt.Errorf("error compact signing with key "+
×
485
                        "locator %v, can only sign with node key", keyLoc)
×
486
        }
×
487

UNCOV
488
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
489
        defer cancel()
×
UNCOV
490

×
UNCOV
491
        resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
×
UNCOV
492
                Msg: msg,
×
UNCOV
493
                KeyLoc: &signrpc.KeyLocator{
×
UNCOV
494
                        KeyFamily: int32(keyLoc.Family),
×
UNCOV
495
                        KeyIndex:  int32(keyLoc.Index),
×
UNCOV
496
                },
×
UNCOV
497
                DoubleHash: doubleHash,
×
UNCOV
498
                CompactSig: true,
×
UNCOV
499
        })
×
UNCOV
500
        if err != nil {
×
501
                considerShutdown(err)
×
502
                return nil, fmt.Errorf("error signing message in remote "+
×
503
                        "signer instance: %v", err)
×
504
        }
×
505

506
        // The signature in the response is zbase32 encoded, so we need to
507
        // decode it before returning.
UNCOV
508
        return resp.Signature, nil
×
509
}
510

511
// SignMessageSchnorr attempts to sign a target message with the private key
512
// described in the key locator. If the target private key is unable to be
513
// found, then an error will be returned. The actual digest signed is the
514
// single or double SHA-256 of the passed message.
515
//
516
// NOTE: This method is part of the keychain.MessageSignerRing interface.
517
func (r *RPCKeyRing) SignMessageSchnorr(keyLoc keychain.KeyLocator,
518
        msg []byte, doubleHash bool, taprootTweak []byte,
UNCOV
519
        tag []byte) (*schnorr.Signature, error) {
×
UNCOV
520

×
UNCOV
521
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
522
        defer cancel()
×
UNCOV
523

×
UNCOV
524
        resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
×
UNCOV
525
                Msg: msg,
×
UNCOV
526
                KeyLoc: &signrpc.KeyLocator{
×
UNCOV
527
                        KeyFamily: int32(keyLoc.Family),
×
UNCOV
528
                        KeyIndex:  int32(keyLoc.Index),
×
UNCOV
529
                },
×
UNCOV
530
                DoubleHash:         doubleHash,
×
UNCOV
531
                SchnorrSig:         true,
×
UNCOV
532
                SchnorrSigTapTweak: taprootTweak,
×
UNCOV
533
                Tag:                tag,
×
UNCOV
534
        })
×
UNCOV
535
        if err != nil {
×
536
                considerShutdown(err)
×
537
                return nil, fmt.Errorf("error signing message in remote "+
×
538
                        "signer instance: %w", err)
×
539
        }
×
540

UNCOV
541
        sigParsed, err := schnorr.ParseSignature(resp.Signature)
×
UNCOV
542
        if err != nil {
×
543
                return nil, fmt.Errorf("can't parse schnorr signature: %w",
×
544
                        err)
×
545
        }
×
UNCOV
546
        return sigParsed, nil
×
547
}
548

549
// DerivePrivKey attempts to derive the private key that corresponds to the
550
// passed key descriptor.  If the public key is set, then this method will
551
// perform an in-order scan over the key set, with a max of MaxKeyRangeScan
552
// keys. In order for this to work, the caller MUST set the KeyFamily within the
553
// partially populated KeyLocator.
554
//
555
// NOTE: This method is part of the keychain.SecretKeyRing interface.
556
func (r *RPCKeyRing) DerivePrivKey(_ keychain.KeyDescriptor) (*btcec.PrivateKey,
557
        error) {
×
558

×
559
        // This operation is not supported with remote signing. There should be
×
560
        // no need for invoking this method unless a channel backup (SCB) file
×
561
        // for pre-0.13.0 channels are attempted to be restored. In that case
×
562
        // it is recommended to restore the channels using a node with the full
×
563
        // seed available.
×
564
        return nil, ErrRemoteSigningPrivateKeyNotAvailable
×
565
}
×
566

567
// SignOutputRaw generates a signature for the passed transaction
568
// according to the data within the passed SignDescriptor.
569
//
570
// NOTE: The resulting signature should be void of a sighash byte.
571
//
572
// NOTE: This method is part of the input.Signer interface.
573
//
574
// NOTE: This method only signs with BIP1017 (internal) keys!
575
func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
UNCOV
576
        signDesc *input.SignDescriptor) (input.Signature, error) {
×
UNCOV
577

×
UNCOV
578
        // Forward the call to the remote signing instance. This call is only
×
UNCOV
579
        // ever called for signing witness (p2pkh or p2wsh) inputs and never
×
UNCOV
580
        // nested witness inputs, so the sigScript is always nil.
×
UNCOV
581
        return r.remoteSign(tx, signDesc, nil)
×
UNCOV
582
}
×
583

584
// ComputeInputScript generates a complete InputIndex for the passed
585
// transaction with the signature as defined within the passed
586
// SignDescriptor. This method should be capable of generating the
587
// proper input script for both regular p2wkh output and p2wkh outputs
588
// nested within a regular p2sh output.
589
//
590
// NOTE: This method will ignore any tweak parameters set within the
591
// passed SignDescriptor as it assumes a set of typical script
592
// templates (p2wkh, np2wkh, BIP0086 p2tr, etc).
593
//
594
// NOTE: This method is part of the input.Signer interface.
595
func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
UNCOV
596
        signDesc *input.SignDescriptor) (*input.Script, error) {
×
UNCOV
597

×
UNCOV
598
        addr, witnessProgram, sigScript, err := r.WalletController.ScriptForOutput(
×
UNCOV
599
                signDesc.Output,
×
UNCOV
600
        )
×
UNCOV
601
        if err != nil {
×
602
                return nil, err
×
603
        }
×
UNCOV
604
        signDesc.WitnessScript = witnessProgram
×
UNCOV
605

×
UNCOV
606
        // If this is a p2tr address, then it must be a BIP0086 key spend if we
×
UNCOV
607
        // are coming through this path (instead of SignOutputRaw).
×
UNCOV
608
        switch addr.AddrType() {
×
UNCOV
609
        case waddrmgr.TaprootPubKey:
×
UNCOV
610
                signDesc.SignMethod = input.TaprootKeySpendBIP0086SignMethod
×
UNCOV
611
                signDesc.WitnessScript = nil
×
UNCOV
612

×
UNCOV
613
                sig, err := r.remoteSign(tx, signDesc, nil)
×
UNCOV
614
                if err != nil {
×
615
                        return nil, fmt.Errorf("error signing with remote"+
×
616
                                "instance: %v", err)
×
617
                }
×
618

UNCOV
619
                rawSig := sig.Serialize()
×
UNCOV
620
                if signDesc.HashType != txscript.SigHashDefault {
×
621
                        rawSig = append(rawSig, byte(signDesc.HashType))
×
622
                }
×
623

UNCOV
624
                return &input.Script{
×
UNCOV
625
                        Witness: wire.TxWitness{
×
UNCOV
626
                                rawSig,
×
UNCOV
627
                        },
×
UNCOV
628
                }, nil
×
629

630
        case waddrmgr.TaprootScript:
×
631
                return nil, fmt.Errorf("computing input script for taproot " +
×
632
                        "script address not supported")
×
633
        }
634

635
        // Let's give the TX to the remote instance now, so it can sign the
636
        // input.
UNCOV
637
        sig, err := r.remoteSign(tx, signDesc, witnessProgram)
×
UNCOV
638
        if err != nil {
×
639
                return nil, fmt.Errorf("error signing with remote instance: %w",
×
640
                        err)
×
641
        }
×
642

643
        // ComputeInputScript currently is only used for P2WKH and NP2WKH
644
        // addresses. So the last item on the stack is always the compressed
645
        // public key.
UNCOV
646
        return &input.Script{
×
UNCOV
647
                Witness: wire.TxWitness{
×
UNCOV
648
                        append(sig.Serialize(), byte(signDesc.HashType)),
×
UNCOV
649
                        addr.PubKey().SerializeCompressed(),
×
UNCOV
650
                },
×
UNCOV
651
                SigScript: sigScript,
×
UNCOV
652
        }, nil
×
653
}
654

655
// MuSig2CreateSession creates a new MuSig2 signing session using the local
656
// key identified by the key locator. The complete list of all public keys of
657
// all signing parties must be provided, including the public key of the local
658
// signing key. If nonces of other parties are already known, they can be
659
// submitted as well to reduce the number of method calls necessary later on.
660
func (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version,
661
        keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey,
662
        tweaks *input.MuSig2Tweaks, otherNonces [][musig2.PubNonceSize]byte,
UNCOV
663
        localNonces *musig2.Nonces) (*input.MuSig2SessionInfo, error) {
×
UNCOV
664

×
UNCOV
665
        apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
×
UNCOV
666
        if err != nil {
×
667
                return nil, err
×
668
        }
×
669

670
        // We need to serialize all data for the RPC call. We can do that by
671
        // putting everything directly into the request struct.
UNCOV
672
        req := &signrpc.MuSig2SessionRequest{
×
UNCOV
673
                KeyLoc: &signrpc.KeyLocator{
×
UNCOV
674
                        KeyFamily: int32(keyLoc.Family),
×
UNCOV
675
                        KeyIndex:  int32(keyLoc.Index),
×
UNCOV
676
                },
×
UNCOV
677
                AllSignerPubkeys: make([][]byte, len(pubKeys)),
×
UNCOV
678
                Tweaks: make(
×
UNCOV
679
                        []*signrpc.TweakDesc, len(tweaks.GenericTweaks),
×
UNCOV
680
                ),
×
UNCOV
681
                OtherSignerPublicNonces: make([][]byte, len(otherNonces)),
×
UNCOV
682
                Version:                 apiVersion,
×
UNCOV
683
        }
×
UNCOV
684
        for idx, pubKey := range pubKeys {
×
UNCOV
685
                switch bipVersion {
×
UNCOV
686
                case input.MuSig2Version040:
×
UNCOV
687
                        req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(
×
UNCOV
688
                                pubKey,
×
UNCOV
689
                        )
×
690

UNCOV
691
                case input.MuSig2Version100RC2:
×
UNCOV
692
                        req.AllSignerPubkeys[idx] = pubKey.SerializeCompressed()
×
693
                }
694
        }
UNCOV
695
        for idx, genericTweak := range tweaks.GenericTweaks {
×
696
                req.Tweaks[idx] = &signrpc.TweakDesc{
×
697
                        Tweak:   genericTweak.Tweak[:],
×
698
                        IsXOnly: genericTweak.IsXOnly,
×
699
                }
×
700
        }
×
UNCOV
701
        for idx, nonce := range otherNonces {
×
UNCOV
702
                req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
×
UNCOV
703
                copy(req.OtherSignerPublicNonces[idx], nonce[:])
×
UNCOV
704
        }
×
UNCOV
705
        if tweaks.HasTaprootTweak() {
×
UNCOV
706
                req.TaprootTweak = &signrpc.TaprootTweakDesc{
×
UNCOV
707
                        KeySpendOnly: tweaks.TaprootBIP0086Tweak,
×
UNCOV
708
                        ScriptRoot:   tweaks.TaprootTweak,
×
UNCOV
709
                }
×
UNCOV
710
        }
×
711

UNCOV
712
        if localNonces != nil {
×
UNCOV
713
                req.PregeneratedLocalNonce = localNonces.SecNonce[:]
×
UNCOV
714
        }
×
715

UNCOV
716
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
717
        defer cancel()
×
UNCOV
718

×
UNCOV
719
        resp, err := r.signerClient.MuSig2CreateSession(ctxt, req)
×
UNCOV
720
        if err != nil {
×
721
                considerShutdown(err)
×
722
                return nil, fmt.Errorf("error creating MuSig2 session in "+
×
723
                        "remote signer instance: %v", err)
×
724
        }
×
725

726
        // De-Serialize all the info back into our native struct.
UNCOV
727
        info := &input.MuSig2SessionInfo{
×
UNCOV
728
                Version:       bipVersion,
×
UNCOV
729
                TaprootTweak:  tweaks.HasTaprootTweak(),
×
UNCOV
730
                HaveAllNonces: resp.HaveAllNonces,
×
UNCOV
731
        }
×
UNCOV
732
        copy(info.SessionID[:], resp.SessionId)
×
UNCOV
733
        copy(info.PublicNonce[:], resp.LocalPublicNonces)
×
UNCOV
734

×
UNCOV
735
        info.CombinedKey, err = schnorr.ParsePubKey(resp.CombinedKey)
×
UNCOV
736
        if err != nil {
×
737
                return nil, fmt.Errorf("error parsing combined key: %w", err)
×
738
        }
×
739

UNCOV
740
        if tweaks.HasTaprootTweak() {
×
UNCOV
741
                info.TaprootInternalKey, err = schnorr.ParsePubKey(
×
UNCOV
742
                        resp.TaprootInternalKey,
×
UNCOV
743
                )
×
UNCOV
744
                if err != nil {
×
745
                        return nil, fmt.Errorf("error parsing internal key: %w",
×
746
                                err)
×
747
                }
×
748
        }
749

UNCOV
750
        return info, nil
×
751
}
752

753
// MuSig2RegisterNonces registers one or more public nonces of other signing
754
// participants for a session identified by its ID. This method returns true
755
// once we have all nonces for all other signing participants.
756
func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
UNCOV
757
        pubNonces [][musig2.PubNonceSize]byte) (bool, error) {
×
UNCOV
758

×
UNCOV
759
        // We need to serialize all data for the RPC call. We can do that by
×
UNCOV
760
        // putting everything directly into the request struct.
×
UNCOV
761
        req := &signrpc.MuSig2RegisterNoncesRequest{
×
UNCOV
762
                SessionId:               sessionID[:],
×
UNCOV
763
                OtherSignerPublicNonces: make([][]byte, len(pubNonces)),
×
UNCOV
764
        }
×
UNCOV
765
        for idx, nonce := range pubNonces {
×
UNCOV
766
                req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
×
UNCOV
767
                copy(req.OtherSignerPublicNonces[idx], nonce[:])
×
UNCOV
768
        }
×
769

UNCOV
770
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
771
        defer cancel()
×
UNCOV
772

×
UNCOV
773
        resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req)
×
UNCOV
774
        if err != nil {
×
775
                considerShutdown(err)
×
776
                return false, fmt.Errorf("error registering MuSig2 nonces in "+
×
777
                        "remote signer instance: %v", err)
×
778
        }
×
779

UNCOV
780
        return resp.HaveAllNonces, nil
×
781
}
782

783
// MuSig2Sign creates a partial signature using the local signing key
784
// that was specified when the session was created. This can only be
785
// called when all public nonces of all participants are known and have
786
// been registered with the session. If this node isn't responsible for
787
// combining all the partial signatures, then the cleanup parameter
788
// should be set, indicating that the session can be removed from memory
789
// once the signature was produced.
790
func (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID,
UNCOV
791
        msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
×
UNCOV
792

×
UNCOV
793
        // We need to serialize all data for the RPC call. We can do that by
×
UNCOV
794
        // putting everything directly into the request struct.
×
UNCOV
795
        req := &signrpc.MuSig2SignRequest{
×
UNCOV
796
                SessionId:     sessionID[:],
×
UNCOV
797
                MessageDigest: msg[:],
×
UNCOV
798
                Cleanup:       cleanUp,
×
UNCOV
799
        }
×
UNCOV
800

×
UNCOV
801
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
802
        defer cancel()
×
UNCOV
803

×
UNCOV
804
        resp, err := r.signerClient.MuSig2Sign(ctxt, req)
×
UNCOV
805
        if err != nil {
×
UNCOV
806
                considerShutdown(err)
×
UNCOV
807
                return nil, fmt.Errorf("error signing MuSig2 session in "+
×
UNCOV
808
                        "remote signer instance: %v", err)
×
UNCOV
809
        }
×
810

UNCOV
811
        partialSig, err := input.DeserializePartialSignature(
×
UNCOV
812
                resp.LocalPartialSignature,
×
UNCOV
813
        )
×
UNCOV
814
        if err != nil {
×
815
                return nil, fmt.Errorf("error parsing partial signature from "+
×
816
                        "remote signer: %v", err)
×
817
        }
×
818

UNCOV
819
        return partialSig, nil
×
820
}
821

822
// MuSig2CombineSig combines the given partial signature(s) with the
823
// local one, if it already exists. Once a partial signature of all
824
// participants is registered, the final signature will be combined and
825
// returned.
826
func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID,
827
        partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
UNCOV
828
        error) {
×
UNCOV
829

×
UNCOV
830
        // We need to serialize all data for the RPC call. We can do that by
×
UNCOV
831
        // putting everything directly into the request struct.
×
UNCOV
832
        req := &signrpc.MuSig2CombineSigRequest{
×
UNCOV
833
                SessionId:              sessionID[:],
×
UNCOV
834
                OtherPartialSignatures: make([][]byte, len(partialSigs)),
×
UNCOV
835
        }
×
UNCOV
836
        for idx, partialSig := range partialSigs {
×
UNCOV
837
                rawSig, err := input.SerializePartialSignature(partialSig)
×
UNCOV
838
                if err != nil {
×
839
                        return nil, false, fmt.Errorf("error serializing "+
×
840
                                "partial signature: %v", err)
×
841
                }
×
UNCOV
842
                req.OtherPartialSignatures[idx] = rawSig[:]
×
843
        }
844

UNCOV
845
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
846
        defer cancel()
×
UNCOV
847

×
UNCOV
848
        resp, err := r.signerClient.MuSig2CombineSig(ctxt, req)
×
UNCOV
849
        if err != nil {
×
850
                considerShutdown(err)
×
851
                return nil, false, fmt.Errorf("error combining MuSig2 "+
×
852
                        "signatures in remote signer instance: %v", err)
×
853
        }
×
854

855
        // The final signature is only available when we have all the other
856
        // partial signatures from all participants.
UNCOV
857
        if !resp.HaveAllSignatures {
×
UNCOV
858
                return nil, resp.HaveAllSignatures, nil
×
UNCOV
859
        }
×
860

UNCOV
861
        finalSig, err := schnorr.ParseSignature(resp.FinalSignature)
×
UNCOV
862
        if err != nil {
×
863
                return nil, false, fmt.Errorf("error parsing final signature: "+
×
864
                        "%v", err)
×
865
        }
×
866

UNCOV
867
        return finalSig, resp.HaveAllSignatures, nil
×
868
}
869

870
// MuSig2Cleanup removes a session from memory to free up resources.
UNCOV
871
func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
×
UNCOV
872
        req := &signrpc.MuSig2CleanupRequest{
×
UNCOV
873
                SessionId: sessionID[:],
×
UNCOV
874
        }
×
UNCOV
875

×
UNCOV
876
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
877
        defer cancel()
×
UNCOV
878

×
UNCOV
879
        _, err := r.signerClient.MuSig2Cleanup(ctxt, req)
×
UNCOV
880
        if err != nil {
×
881
                considerShutdown(err)
×
882
                return fmt.Errorf("error cleaning up MuSig2 session in remote "+
×
883
                        "signer instance: %v", err)
×
884
        }
×
885

UNCOV
886
        return nil
×
887
}
888

889
// remoteSign signs the input specified in signDesc of the given transaction tx
890
// using the remote signing instance.
891
func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
UNCOV
892
        sigScript []byte) (input.Signature, error) {
×
UNCOV
893

×
UNCOV
894
        packet, err := packetFromTx(tx)
×
UNCOV
895
        if err != nil {
×
896
                return nil, fmt.Errorf("error converting TX into PSBT: %w", err)
×
897
        }
×
898

899
        // We need to add witness information for all inputs! Otherwise, we'll
900
        // have a problem when attempting to sign a taproot input!
UNCOV
901
        for idx := range packet.Inputs {
×
UNCOV
902
                // Skip the input we're signing for, that will get a special
×
UNCOV
903
                // treatment later on.
×
UNCOV
904
                if idx == signDesc.InputIndex {
×
UNCOV
905
                        continue
×
906
                }
907

UNCOV
908
                txIn := tx.TxIn[idx]
×
UNCOV
909
                info, err := r.WalletController.FetchOutpointInfo(
×
UNCOV
910
                        &txIn.PreviousOutPoint,
×
UNCOV
911
                )
×
UNCOV
912
                if err != nil {
×
913
                        // Maybe we have an UTXO in the previous output fetcher?
×
914
                        if signDesc.PrevOutputFetcher != nil {
×
915
                                utxo := signDesc.PrevOutputFetcher.FetchPrevOutput(
×
916
                                        txIn.PreviousOutPoint,
×
917
                                )
×
918
                                if utxo != nil && utxo.Value != 0 &&
×
919
                                        len(utxo.PkScript) > 0 {
×
920

×
921
                                        packet.Inputs[idx].WitnessUtxo = utxo
×
922
                                        continue
×
923
                                }
924
                        }
925

926
                        log.Warnf("No UTXO info found for index %d "+
×
927
                                "(prev_outpoint=%v), won't be able to sign "+
×
928
                                "for taproot output!", idx,
×
929
                                txIn.PreviousOutPoint)
×
930
                        continue
×
931
                }
UNCOV
932
                packet.Inputs[idx].WitnessUtxo = &wire.TxOut{
×
UNCOV
933
                        Value:    int64(info.Value),
×
UNCOV
934
                        PkScript: info.PkScript,
×
UNCOV
935
                }
×
936
        }
937

938
        // Catch incorrect signing input index, just in case.
UNCOV
939
        if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) {
×
940
                return nil, fmt.Errorf("invalid input index in sign descriptor")
×
941
        }
×
UNCOV
942
        in := &packet.Inputs[signDesc.InputIndex]
×
UNCOV
943
        txIn := tx.TxIn[signDesc.InputIndex]
×
UNCOV
944

×
UNCOV
945
        // Things are a bit tricky with the sign descriptor. There basically are
×
UNCOV
946
        // four ways to describe a key:
×
UNCOV
947
        //   1. By public key only. To match this case both family and index
×
UNCOV
948
        //      must be set to 0.
×
UNCOV
949
        //   2. By family and index only. To match this case the public key
×
UNCOV
950
        //      must be nil and either the family or index must be non-zero.
×
UNCOV
951
        //   3. All values are set and locator is non-empty. To match this case
×
UNCOV
952
        //      the public key must be set and either the family or index must
×
UNCOV
953
        //      be non-zero.
×
UNCOV
954
        //   4. All values are set and locator is empty. This is a special case
×
UNCOV
955
        //      for the very first channel ever created (with the multi-sig key
×
UNCOV
956
        //      family which is 0 and the index which is 0 as well). This looks
×
UNCOV
957
        //      identical to case 1 and will also be handled like that case.
×
UNCOV
958
        // We only really handle case 1 and 2 here, since 3 is no problem and 4
×
UNCOV
959
        // is identical to 1.
×
UNCOV
960
        switch {
×
961
        // Case 1: Public key only. We need to find out the derivation path for
962
        // this public key by asking the wallet. This is only possible for our
963
        // internal, custom 1017 scope since we know all keys derived there are
964
        // internally stored as p2wkh addresses.
UNCOV
965
        case signDesc.KeyDesc.PubKey != nil && signDesc.KeyDesc.IsEmpty():
×
UNCOV
966
                pubKeyBytes := signDesc.KeyDesc.PubKey.SerializeCompressed()
×
UNCOV
967
                addr, err := btcutil.NewAddressWitnessPubKeyHash(
×
UNCOV
968
                        btcutil.Hash160(pubKeyBytes), r.netParams,
×
UNCOV
969
                )
×
UNCOV
970
                if err != nil {
×
971
                        return nil, fmt.Errorf("error deriving address from "+
×
972
                                "public key %x: %v", pubKeyBytes, err)
×
973
                }
×
974

UNCOV
975
                managedAddr, err := r.AddressInfo(addr)
×
UNCOV
976
                if err != nil {
×
977
                        return nil, fmt.Errorf("error fetching address info "+
×
978
                                "for public key %x: %v", pubKeyBytes, err)
×
979
                }
×
980

UNCOV
981
                pubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
×
UNCOV
982
                if !ok {
×
983
                        return nil, fmt.Errorf("address derived for public "+
×
984
                                "key %x is not a p2wkh address", pubKeyBytes)
×
985
                }
×
986

UNCOV
987
                scope, path, _ := pubKeyAddr.DerivationInfo()
×
UNCOV
988
                if scope.Purpose != keychain.BIP0043Purpose {
×
989
                        return nil, fmt.Errorf("address derived for public "+
×
990
                                "key %x is not in custom key scope %d'",
×
991
                                pubKeyBytes, keychain.BIP0043Purpose)
×
992
                }
×
993

994
                // We now have all the information we need to complete our key
995
                // locator information.
UNCOV
996
                signDesc.KeyDesc.KeyLocator = keychain.KeyLocator{
×
UNCOV
997
                        Family: keychain.KeyFamily(path.InternalAccount),
×
UNCOV
998
                        Index:  path.Index,
×
UNCOV
999
                }
×
1000

1001
        // Case 2: Family and index only. This case is easy, we can just go
1002
        // ahead and derive the public key from the family and index and then
1003
        // supply that information in the BIP32 derivation field.
UNCOV
1004
        case signDesc.KeyDesc.PubKey == nil && !signDesc.KeyDesc.IsEmpty():
×
UNCOV
1005
                fullDesc, err := r.watchOnlyKeyRing.DeriveKey(
×
UNCOV
1006
                        signDesc.KeyDesc.KeyLocator,
×
UNCOV
1007
                )
×
UNCOV
1008
                if err != nil {
×
1009
                        return nil, fmt.Errorf("error deriving key with "+
×
1010
                                "family %d and index %d from the watch-only "+
×
1011
                                "wallet: %v",
×
1012
                                signDesc.KeyDesc.KeyLocator.Family,
×
1013
                                signDesc.KeyDesc.KeyLocator.Index, err)
×
1014
                }
×
UNCOV
1015
                signDesc.KeyDesc.PubKey = fullDesc.PubKey
×
1016
        }
1017

UNCOV
1018
        var derivation *psbt.Bip32Derivation
×
UNCOV
1019

×
UNCOV
1020
        // Make sure we actually know about the input. We either have been
×
UNCOV
1021
        // watching the UTXO on-chain or we have been given all the required
×
UNCOV
1022
        // info in the sign descriptor.
×
UNCOV
1023
        info, err := r.WalletController.FetchOutpointInfo(
×
UNCOV
1024
                &txIn.PreviousOutPoint,
×
UNCOV
1025
        )
×
UNCOV
1026

×
UNCOV
1027
        // If the wallet is aware of this outpoint, we go ahead and fetch the
×
UNCOV
1028
        // derivation info.
×
UNCOV
1029
        if err == nil {
×
UNCOV
1030
                derivation, err = r.WalletController.FetchDerivationInfo(
×
UNCOV
1031
                        info.PkScript,
×
UNCOV
1032
                )
×
UNCOV
1033
        }
×
1034

UNCOV
1035
        switch {
×
1036
        // No error, we do have the full UTXO info available.
UNCOV
1037
        case err == nil:
×
UNCOV
1038
                in.WitnessUtxo = &wire.TxOut{
×
UNCOV
1039
                        Value:    int64(info.Value),
×
UNCOV
1040
                        PkScript: info.PkScript,
×
UNCOV
1041
                }
×
UNCOV
1042
                in.NonWitnessUtxo = info.PrevTx
×
UNCOV
1043
                in.Bip32Derivation = []*psbt.Bip32Derivation{derivation}
×
1044

1045
        // The wallet doesn't know about this UTXO, so it's probably a TX that
1046
        // we haven't published yet (e.g. a channel funding TX). So we need to
1047
        // assemble everything from the sign descriptor. We won't be able to
1048
        // supply a non-witness UTXO (=full TX of the input being spent) in this
1049
        // case. That is no problem if the signing instance is another lnd
1050
        // instance since we don't require it for pure witness inputs. But a
1051
        // hardware wallet might require it for security reasons.
UNCOV
1052
        case signDesc.KeyDesc.PubKey != nil && signDesc.Output != nil:
×
UNCOV
1053
                in.WitnessUtxo = signDesc.Output
×
UNCOV
1054
                in.Bip32Derivation = []*psbt.Bip32Derivation{{
×
UNCOV
1055
                        Bip32Path: []uint32{
×
UNCOV
1056
                                keychain.BIP0043Purpose +
×
UNCOV
1057
                                        hdkeychain.HardenedKeyStart,
×
UNCOV
1058
                                r.netParams.HDCoinType +
×
UNCOV
1059
                                        hdkeychain.HardenedKeyStart,
×
UNCOV
1060
                                uint32(signDesc.KeyDesc.Family) +
×
UNCOV
1061
                                        hdkeychain.HardenedKeyStart,
×
UNCOV
1062
                                0,
×
UNCOV
1063
                                signDesc.KeyDesc.Index,
×
UNCOV
1064
                        },
×
UNCOV
1065
                        PubKey: signDesc.KeyDesc.PubKey.SerializeCompressed(),
×
UNCOV
1066
                }}
×
UNCOV
1067

×
UNCOV
1068
                // We need to specify a pk script in the witness UTXO, otherwise
×
UNCOV
1069
                // the field becomes invalid when serialized as a PSBT. To avoid
×
UNCOV
1070
                // running into a generic "Invalid PSBT serialization format"
×
UNCOV
1071
                // error later, we return a more descriptive error now.
×
UNCOV
1072
                if len(in.WitnessUtxo.PkScript) == 0 {
×
1073
                        return nil, fmt.Errorf("error assembling UTXO " +
×
1074
                                "information, output not known to wallet and " +
×
1075
                                "no UTXO pk script provided in sign descriptor")
×
1076
                }
×
1077

1078
        default:
×
1079
                return nil, fmt.Errorf("error assembling UTXO information, "+
×
1080
                        "wallet returned err='%v' and sign descriptor is "+
×
1081
                        "incomplete", err)
×
1082
        }
1083

1084
        // Assemble all other information about the input we have.
UNCOV
1085
        in.RedeemScript = sigScript
×
UNCOV
1086
        in.SighashType = signDesc.HashType
×
UNCOV
1087
        in.WitnessScript = signDesc.WitnessScript
×
UNCOV
1088

×
UNCOV
1089
        if len(signDesc.SingleTweak) > 0 {
×
UNCOV
1090
                in.Unknowns = append(in.Unknowns, &psbt.Unknown{
×
UNCOV
1091
                        Key:   btcwallet.PsbtKeyTypeInputSignatureTweakSingle,
×
UNCOV
1092
                        Value: signDesc.SingleTweak,
×
UNCOV
1093
                })
×
UNCOV
1094
        }
×
UNCOV
1095
        if signDesc.DoubleTweak != nil {
×
1096
                in.Unknowns = append(in.Unknowns, &psbt.Unknown{
×
1097
                        Key:   btcwallet.PsbtKeyTypeInputSignatureTweakDouble,
×
1098
                        Value: signDesc.DoubleTweak.Serialize(),
×
1099
                })
×
1100
        }
×
1101

1102
        // Add taproot specific fields.
UNCOV
1103
        switch signDesc.SignMethod {
×
1104
        case input.TaprootKeySpendBIP0086SignMethod,
UNCOV
1105
                input.TaprootKeySpendSignMethod:
×
UNCOV
1106

×
UNCOV
1107
                // The key identifying factor for a key spend is that we don't
×
UNCOV
1108
                // provide any leaf hashes to signal we want a signature for the
×
UNCOV
1109
                // key spend path (with the internal key).
×
UNCOV
1110
                d := in.Bip32Derivation[0]
×
UNCOV
1111
                in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
×
UNCOV
1112
                        // The x-only public key is just our compressed public
×
UNCOV
1113
                        // key without the first byte (type/parity).
×
UNCOV
1114
                        XOnlyPubKey:          d.PubKey[1:],
×
UNCOV
1115
                        LeafHashes:           nil,
×
UNCOV
1116
                        MasterKeyFingerprint: d.MasterKeyFingerprint,
×
UNCOV
1117
                        Bip32Path:            d.Bip32Path,
×
UNCOV
1118
                }}
×
UNCOV
1119

×
UNCOV
1120
                // If this is a BIP0086 key spend then the tap tweak is empty,
×
UNCOV
1121
                // otherwise it's set to the Taproot root hash.
×
UNCOV
1122
                in.TaprootMerkleRoot = signDesc.TapTweak
×
1123

UNCOV
1124
        case input.TaprootScriptSpendSignMethod:
×
UNCOV
1125
                // The script spend path is a bit more involved when doing it
×
UNCOV
1126
                // through the PSBT method. We need to specify the leaf hash
×
UNCOV
1127
                // that the signer should sign for.
×
UNCOV
1128
                leaf := txscript.TapLeaf{
×
UNCOV
1129
                        LeafVersion: txscript.BaseLeafVersion,
×
UNCOV
1130
                        Script:      signDesc.WitnessScript,
×
UNCOV
1131
                }
×
UNCOV
1132
                leafHash := leaf.TapHash()
×
UNCOV
1133

×
UNCOV
1134
                d := in.Bip32Derivation[0]
×
UNCOV
1135
                in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
×
UNCOV
1136
                        XOnlyPubKey:          d.PubKey[1:],
×
UNCOV
1137
                        LeafHashes:           [][]byte{leafHash[:]},
×
UNCOV
1138
                        MasterKeyFingerprint: d.MasterKeyFingerprint,
×
UNCOV
1139
                        Bip32Path:            d.Bip32Path,
×
UNCOV
1140
                }}
×
UNCOV
1141

×
UNCOV
1142
                // We also need to supply a control block. But because we don't
×
UNCOV
1143
                // know the internal key nor the merkle proofs (both is not
×
UNCOV
1144
                // supplied through the SignOutputRaw RPC) and is technically
×
UNCOV
1145
                // not really needed by the signer (since we only want a
×
UNCOV
1146
                // signature, the full witness stack is assembled by the caller
×
UNCOV
1147
                // of this RPC), we can get by with faking certain information
×
UNCOV
1148
                // that we don't have.
×
UNCOV
1149
                fakeInternalKey, _ := btcec.ParsePubKey(d.PubKey)
×
UNCOV
1150
                fakeKeyIsOdd := d.PubKey[0] == input.PubKeyFormatCompressedOdd
×
UNCOV
1151
                controlBlock := txscript.ControlBlock{
×
UNCOV
1152
                        InternalKey:     fakeInternalKey,
×
UNCOV
1153
                        OutputKeyYIsOdd: fakeKeyIsOdd,
×
UNCOV
1154
                        LeafVersion:     leaf.LeafVersion,
×
UNCOV
1155
                }
×
UNCOV
1156
                blockBytes, err := controlBlock.ToBytes()
×
UNCOV
1157
                if err != nil {
×
1158
                        return nil, fmt.Errorf("error serializing control "+
×
1159
                                "block: %v", err)
×
1160
                }
×
1161

UNCOV
1162
                in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{
×
UNCOV
1163
                        ControlBlock: blockBytes,
×
UNCOV
1164
                        Script:       leaf.Script,
×
UNCOV
1165
                        LeafVersion:  leaf.LeafVersion,
×
UNCOV
1166
                }}
×
1167
        }
1168

1169
        // Okay, let's sign the input by the remote signer now.
UNCOV
1170
        ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
×
UNCOV
1171
        defer cancel()
×
UNCOV
1172

×
UNCOV
1173
        var buf bytes.Buffer
×
UNCOV
1174
        if err := packet.Serialize(&buf); err != nil {
×
1175
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
1176
        }
×
1177

UNCOV
1178
        resp, err := r.walletClient.SignPsbt(
×
UNCOV
1179
                ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()},
×
UNCOV
1180
        )
×
UNCOV
1181
        if err != nil {
×
1182
                considerShutdown(err)
×
1183
                return nil, fmt.Errorf("error signing PSBT in remote signer "+
×
1184
                        "instance: %v", err)
×
1185
        }
×
1186

UNCOV
1187
        signedPacket, err := psbt.NewFromRawBytes(
×
UNCOV
1188
                bytes.NewReader(resp.SignedPsbt), false,
×
UNCOV
1189
        )
×
UNCOV
1190
        if err != nil {
×
1191
                return nil, fmt.Errorf("error parsing signed PSBT: %w", err)
×
1192
        }
×
1193

1194
        // We expect a signature in the input now.
UNCOV
1195
        if signDesc.InputIndex >= len(signedPacket.Inputs) {
×
1196
                return nil, fmt.Errorf("remote signer returned invalid PSBT")
×
1197
        }
×
UNCOV
1198
        in = &signedPacket.Inputs[signDesc.InputIndex]
×
UNCOV
1199

×
UNCOV
1200
        return extractSignature(in, signDesc.SignMethod)
×
1201
}
1202

1203
// extractSignature attempts to extract the signature from the PSBT input,
1204
// looking at different fields depending on the signing method that was used.
1205
func extractSignature(in *psbt.PInput,
UNCOV
1206
        signMethod input.SignMethod) (input.Signature, error) {
×
UNCOV
1207

×
UNCOV
1208
        switch signMethod {
×
UNCOV
1209
        case input.WitnessV0SignMethod:
×
UNCOV
1210
                if len(in.PartialSigs) != 1 {
×
1211
                        return nil, fmt.Errorf("remote signer returned "+
×
1212
                                "invalid partial signature, wanted 1, got %d",
×
1213
                                len(in.PartialSigs))
×
1214
                }
×
UNCOV
1215
                sigWithSigHash := in.PartialSigs[0]
×
UNCOV
1216
                if sigWithSigHash == nil {
×
1217
                        return nil, fmt.Errorf("remote signer returned nil " +
×
1218
                                "signature")
×
1219
                }
×
1220

1221
                // The remote signer always adds the sighash type, so we need to
1222
                // account for that.
UNCOV
1223
                sigLen := len(sigWithSigHash.Signature)
×
UNCOV
1224
                if sigLen < ecdsa.MinSigLen+1 {
×
1225
                        return nil, fmt.Errorf("remote signer returned "+
×
1226
                                "invalid partial signature: signature too "+
×
1227
                                "short with %d bytes", sigLen)
×
1228
                }
×
1229

1230
                // Parse the signature, but chop off the last byte which is the
1231
                // sighash type.
UNCOV
1232
                sig := sigWithSigHash.Signature[0 : sigLen-1]
×
UNCOV
1233
                return ecdsa.ParseDERSignature(sig)
×
1234

1235
        // The type of key spend doesn't matter, the signature should be in the
1236
        // same field for both of those signing methods.
1237
        case input.TaprootKeySpendBIP0086SignMethod,
UNCOV
1238
                input.TaprootKeySpendSignMethod:
×
UNCOV
1239

×
UNCOV
1240
                sigLen := len(in.TaprootKeySpendSig)
×
UNCOV
1241
                if sigLen < schnorr.SignatureSize {
×
1242
                        return nil, fmt.Errorf("remote signer returned "+
×
1243
                                "invalid key spend signature: signature too "+
×
1244
                                "short with %d bytes", sigLen)
×
1245
                }
×
1246

UNCOV
1247
                return schnorr.ParseSignature(
×
UNCOV
1248
                        in.TaprootKeySpendSig[:schnorr.SignatureSize],
×
UNCOV
1249
                )
×
1250

UNCOV
1251
        case input.TaprootScriptSpendSignMethod:
×
UNCOV
1252
                if len(in.TaprootScriptSpendSig) != 1 {
×
1253
                        return nil, fmt.Errorf("remote signer returned "+
×
1254
                                "invalid taproot script spend signature, "+
×
1255
                                "wanted 1, got %d",
×
1256
                                len(in.TaprootScriptSpendSig))
×
1257
                }
×
UNCOV
1258
                scriptSpendSig := in.TaprootScriptSpendSig[0]
×
UNCOV
1259
                if scriptSpendSig == nil {
×
1260
                        return nil, fmt.Errorf("remote signer returned nil " +
×
1261
                                "taproot script spend signature")
×
1262
                }
×
1263

UNCOV
1264
                return schnorr.ParseSignature(scriptSpendSig.Signature)
×
1265

1266
        default:
×
1267
                return nil, fmt.Errorf("can't extract signature, unsupported "+
×
1268
                        "signing method: %v", signMethod)
×
1269
        }
1270
}
1271

1272
// connectRPC tries to establish an RPC connection to the given host:port with
1273
// the supplied certificate and macaroon.
1274
func connectRPC(hostPort, tlsCertPath, macaroonPath string,
UNCOV
1275
        timeout time.Duration) (*grpc.ClientConn, error) {
×
UNCOV
1276

×
UNCOV
1277
        certBytes, err := os.ReadFile(tlsCertPath)
×
UNCOV
1278
        if err != nil {
×
1279
                return nil, fmt.Errorf("error reading TLS cert file %v: %w",
×
1280
                        tlsCertPath, err)
×
1281
        }
×
1282

UNCOV
1283
        cp := x509.NewCertPool()
×
UNCOV
1284
        if !cp.AppendCertsFromPEM(certBytes) {
×
1285
                return nil, fmt.Errorf("credentials: failed to append " +
×
1286
                        "certificate")
×
1287
        }
×
1288

UNCOV
1289
        macBytes, err := os.ReadFile(macaroonPath)
×
UNCOV
1290
        if err != nil {
×
1291
                return nil, fmt.Errorf("error reading macaroon file %v: %w",
×
1292
                        macaroonPath, err)
×
1293
        }
×
UNCOV
1294
        mac := &macaroon.Macaroon{}
×
UNCOV
1295
        if err := mac.UnmarshalBinary(macBytes); err != nil {
×
1296
                return nil, fmt.Errorf("error decoding macaroon: %w", err)
×
1297
        }
×
1298

UNCOV
1299
        macCred, err := macaroons.NewMacaroonCredential(mac)
×
UNCOV
1300
        if err != nil {
×
1301
                return nil, fmt.Errorf("error creating creds: %w", err)
×
1302
        }
×
1303

UNCOV
1304
        opts := []grpc.DialOption{
×
UNCOV
1305
                grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(
×
UNCOV
1306
                        cp, "",
×
UNCOV
1307
                )),
×
UNCOV
1308
                grpc.WithPerRPCCredentials(macCred),
×
UNCOV
1309
                grpc.WithBlock(),
×
UNCOV
1310
        }
×
UNCOV
1311
        ctxt, cancel := context.WithTimeout(context.Background(), timeout)
×
UNCOV
1312
        defer cancel()
×
UNCOV
1313
        conn, err := grpc.DialContext(ctxt, hostPort, opts...)
×
UNCOV
1314
        if err != nil {
×
1315
                return nil, fmt.Errorf("unable to connect to RPC server: %w",
×
1316
                        err)
×
1317
        }
×
1318

UNCOV
1319
        return conn, nil
×
1320
}
1321

1322
// packetFromTx creates a PSBT from a tx that potentially already contains
1323
// signed inputs.
UNCOV
1324
func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) {
×
UNCOV
1325
        // The psbt.NewFromUnsignedTx function complains if there are any
×
UNCOV
1326
        // scripts or witness content on a TX. So we create a copy of the TX and
×
UNCOV
1327
        // nil out all the offending data, but also keep a backup around that we
×
UNCOV
1328
        // add to the PSBT afterwards.
×
UNCOV
1329
        noSigs := original.Copy()
×
UNCOV
1330
        for idx := range noSigs.TxIn {
×
UNCOV
1331
                noSigs.TxIn[idx].SignatureScript = nil
×
UNCOV
1332
                noSigs.TxIn[idx].Witness = nil
×
UNCOV
1333
        }
×
1334

1335
        // With all the data that is seen as "signed", we can now create the
1336
        // empty packet.
UNCOV
1337
        packet, err := psbt.NewFromUnsignedTx(noSigs)
×
UNCOV
1338
        if err != nil {
×
1339
                return nil, err
×
1340
        }
×
1341

UNCOV
1342
        var buf bytes.Buffer
×
UNCOV
1343
        for idx, txIn := range original.TxIn {
×
UNCOV
1344
                if len(txIn.SignatureScript) > 0 {
×
1345
                        packet.Inputs[idx].FinalScriptSig = txIn.SignatureScript
×
1346
                }
×
1347

UNCOV
1348
                if len(txIn.Witness) > 0 {
×
UNCOV
1349
                        buf.Reset()
×
UNCOV
1350
                        err = psbt.WriteTxWitness(&buf, txIn.Witness)
×
UNCOV
1351
                        if err != nil {
×
1352
                                return nil, err
×
1353
                        }
×
UNCOV
1354
                        packet.Inputs[idx].FinalScriptWitness = buf.Bytes()
×
1355
                }
1356
        }
1357

UNCOV
1358
        return packet, nil
×
1359
}
1360

1361
// considerShutdown inspects the error and issues a shutdown (through logging
1362
// a critical error, which will cause the logger to issue a clean shutdown
1363
// request) if the error looks like a connection or general availability error
1364
// and not some application specific problem.
UNCOV
1365
func considerShutdown(err error) {
×
UNCOV
1366
        statusErr, isStatusErr := status.FromError(err)
×
UNCOV
1367
        switch {
×
1368
        // The context attached to the client request has timed out. This can be
1369
        // due to not being able to reach the signing server, or it's taking too
1370
        // long to respond. In either case, request a shutdown.
1371
        case err == context.DeadlineExceeded:
×
1372
                fallthrough
×
1373

1374
        // The signing server's context timed out before the client's due to
1375
        // clock skew, request a shutdown anyway.
1376
        case isStatusErr && statusErr.Code() == codes.DeadlineExceeded:
×
1377
                log.Critical("RPC signing timed out: %v", err)
×
1378

1379
        case isStatusErr && statusErr.Code() == codes.Unavailable:
×
1380
                log.Critical("RPC signing server not available: %v", err)
×
1381
        }
1382
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc