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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 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"
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 {
×
UNCOV
424
                considerShutdown(err)
×
UNCOV
425
                return key, fmt.Errorf("error deriving shared key in remote "+
×
UNCOV
426
                        "signer instance: %v", err)
×
UNCOV
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

UNCOV
1379
        case isStatusErr && statusErr.Code() == codes.Unavailable:
×
UNCOV
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