Skip to content

Commit 4602d41

Browse files
authored
Merge pull request #203 from lightninglabs/sign-output-raw-fix
signer: create workaround for SignOutputRaw quirk
2 parents a412e17 + 38c18e2 commit 4602d41

File tree

2 files changed

+113
-40
lines changed

2 files changed

+113
-40
lines changed

macaroon_recipes.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,24 @@ var (
2727
// implemented in lndclient and the value is the original name of the
2828
// RPC method defined in the proto.
2929
renames = map[string]string{
30-
"ChannelBackup": "ExportChannelBackup",
31-
"ChannelBackups": "ExportAllChannelBackups",
32-
"ConfirmedWalletBalance": "WalletBalance",
33-
"Connect": "ConnectPeer",
34-
"DecodePaymentRequest": "DecodePayReq",
35-
"ListTransactions": "GetTransactions",
36-
"PayInvoice": "SendPaymentSync",
37-
"UpdateChanPolicy": "UpdateChannelPolicy",
38-
"NetworkInfo": "GetNetworkInfo",
39-
"SubscribeGraph": "SubscribeChannelGraph",
40-
"InterceptHtlcs": "HtlcInterceptor",
41-
"ImportMissionControl": "XImportMissionControl",
42-
"EstimateFeeRate": "EstimateFee",
43-
"EstimateFeeToP2WSH": "EstimateFee",
44-
"OpenChannelStream": "OpenChannel",
45-
"ListSweepsVerbose": "ListSweeps",
46-
"MinRelayFee": "EstimateFee",
30+
"ChannelBackup": "ExportChannelBackup",
31+
"ChannelBackups": "ExportAllChannelBackups",
32+
"ConfirmedWalletBalance": "WalletBalance",
33+
"Connect": "ConnectPeer",
34+
"DecodePaymentRequest": "DecodePayReq",
35+
"ListTransactions": "GetTransactions",
36+
"PayInvoice": "SendPaymentSync",
37+
"UpdateChanPolicy": "UpdateChannelPolicy",
38+
"NetworkInfo": "GetNetworkInfo",
39+
"SubscribeGraph": "SubscribeChannelGraph",
40+
"InterceptHtlcs": "HtlcInterceptor",
41+
"ImportMissionControl": "XImportMissionControl",
42+
"EstimateFeeRate": "EstimateFee",
43+
"EstimateFeeToP2WSH": "EstimateFee",
44+
"OpenChannelStream": "OpenChannel",
45+
"ListSweepsVerbose": "ListSweeps",
46+
"MinRelayFee": "EstimateFee",
47+
"SignOutputRawKeyLocator": "SignOutputRaw",
4748
}
4849

4950
// ignores is a list of method names on the client implementations that

signer_client.go

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ type SignerClient interface {
2828
signDescriptors []*SignDescriptor,
2929
prevOutputs []*wire.TxOut) ([][]byte, error)
3030

31+
// SignOutputRawKeyLocator is a copy of the SignOutputRaw that fixes a
32+
// specific issue around how the key locator is populated in the sign
33+
// descriptor. We copy this method instead of fixing the original to
34+
// make sure we don't break any existing applications that have already
35+
// adjusted themselves to use the specific behavior of the original
36+
// SignOutputRaw method.
37+
SignOutputRawKeyLocator(ctx context.Context, tx *wire.MsgTx,
38+
signDescriptors []*SignDescriptor,
39+
prevOutputs []*wire.TxOut) ([][]byte, error)
40+
3141
// ComputeInputScript generates the proper input script for P2WPKH
3242
// output and NP2WPKH outputs. This method only requires that the
3343
// `Output`, `HashType`, `SigHashes` and `InputIndex` fields are
@@ -215,26 +225,70 @@ func (s *signerClient) RawClientWithMacAuth(
215225
return s.signerMac.WithMacaroonAuth(parentCtx), s.timeout, s.client
216226
}
217227

218-
func marshallSignDescriptors(
219-
signDescriptors []*SignDescriptor) []*signrpc.SignDescriptor {
228+
func marshallSignDescriptors(signDescriptors []*SignDescriptor,
229+
fullDescriptors bool) []*signrpc.SignDescriptor {
220230

221-
rpcSignDescs := make([]*signrpc.SignDescriptor, len(signDescriptors))
222-
for i, signDesc := range signDescriptors {
223-
var keyBytes []byte
224-
var keyLocator *signrpc.KeyLocator
225-
if signDesc.KeyDesc.PubKey != nil {
226-
keyBytes = signDesc.KeyDesc.PubKey.SerializeCompressed()
231+
// partialDescriptor is a helper method that creates a partially
232+
// populated sign descriptor that is backward compatible with the way
233+
// some applications like Loop expect the call to lnd to be made. This
234+
// function only populates _either_ the public key or the key locator in
235+
// the descriptor, but not both.
236+
partialDescriptor := func(
237+
d keychain.KeyDescriptor) *signrpc.KeyDescriptor {
238+
239+
keyDesc := &signrpc.KeyDescriptor{}
240+
if d.PubKey != nil {
241+
keyDesc.RawKeyBytes = d.PubKey.SerializeCompressed()
227242
} else {
228-
keyLocator = &signrpc.KeyLocator{
229-
KeyFamily: int32(
230-
signDesc.KeyDesc.KeyLocator.Family,
231-
),
232-
KeyIndex: int32(
233-
signDesc.KeyDesc.KeyLocator.Index,
234-
),
243+
keyDesc.KeyLoc = &signrpc.KeyLocator{
244+
KeyFamily: int32(d.KeyLocator.Family),
245+
KeyIndex: int32(d.KeyLocator.Index),
235246
}
236247
}
237248

249+
return keyDesc
250+
}
251+
252+
// fullDescriptor is a helper method that creates a fully populated sign
253+
// descriptor that includes both the public key and the key locator (if
254+
// available). For the locator we explicitly check that both the family
255+
// _and_ the index is non-zero. In some applications it's possible that
256+
// the family is always set (because only a specific family is used),
257+
// but the index might be zero because it's the first key, or because it
258+
// isn't known at that particular moment.
259+
// We aim to be compatible with this method in lnd's wallet:
260+
// https://github.com/lightningnetwork/lnd/blob/master/lnwallet/btcwallet/signer.go#L286
261+
// Because we know all custom families (0 to 255) are derived at wallet
262+
// creation, and the very first index of each family/account is always
263+
// derived, we know that only using the public key for that very first
264+
// index will work. But for a freshly initialized wallet (e.g. restored
265+
// from seed), we won't know any indexes greater than 0, so we _need_ to
266+
// also specify the key locator and not just the public key.
267+
fullDescriptor := func(
268+
d keychain.KeyDescriptor) *signrpc.KeyDescriptor {
269+
270+
keyDesc := &signrpc.KeyDescriptor{}
271+
if d.PubKey != nil {
272+
keyDesc.RawKeyBytes = d.PubKey.SerializeCompressed()
273+
}
274+
275+
if d.KeyLocator.Family != 0 && d.KeyLocator.Index != 0 {
276+
keyDesc.KeyLoc = &signrpc.KeyLocator{
277+
KeyFamily: int32(d.KeyLocator.Family),
278+
KeyIndex: int32(d.KeyLocator.Index),
279+
}
280+
}
281+
282+
return keyDesc
283+
}
284+
285+
rpcSignDescs := make([]*signrpc.SignDescriptor, len(signDescriptors))
286+
for i, signDesc := range signDescriptors {
287+
keyDesc := partialDescriptor(signDesc.KeyDesc)
288+
if fullDescriptors {
289+
keyDesc = fullDescriptor(signDesc.KeyDesc)
290+
}
291+
238292
var doubleTweak []byte
239293
if signDesc.DoubleTweak != nil {
240294
doubleTweak = signDesc.DoubleTweak.Serialize()
@@ -247,12 +301,9 @@ func marshallSignDescriptors(
247301
PkScript: signDesc.Output.PkScript,
248302
Value: signDesc.Output.Value,
249303
},
250-
Sighash: uint32(signDesc.HashType),
251-
InputIndex: int32(signDesc.InputIndex),
252-
KeyDesc: &signrpc.KeyDescriptor{
253-
RawKeyBytes: keyBytes,
254-
KeyLoc: keyLocator,
255-
},
304+
Sighash: uint32(signDesc.HashType),
305+
InputIndex: int32(signDesc.InputIndex),
306+
KeyDesc: keyDesc,
256307
SingleTweak: signDesc.SingleTweak,
257308
DoubleTweak: doubleTweak,
258309
TapTweak: signDesc.TapTweak,
@@ -283,11 +334,32 @@ func (s *signerClient) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
283334
signDescriptors []*SignDescriptor, prevOutputs []*wire.TxOut) ([][]byte,
284335
error) {
285336

337+
return s.signOutputRaw(ctx, tx, signDescriptors, prevOutputs, false)
338+
}
339+
340+
// SignOutputRawKeyLocator is a copy of the SignOutputRaw that fixes a specific
341+
// issue around how the key locator is populated in the sign descriptor. We copy
342+
// this method instead of fixing the original to make sure we don't break any
343+
// existing applications that have already adjusted themselves to use the
344+
// specific behavior of the original SignOutputRaw method.
345+
func (s *signerClient) SignOutputRawKeyLocator(ctx context.Context,
346+
tx *wire.MsgTx, signDescriptors []*SignDescriptor,
347+
prevOutputs []*wire.TxOut) ([][]byte, error) {
348+
349+
return s.signOutputRaw(ctx, tx, signDescriptors, prevOutputs, true)
350+
}
351+
352+
// signOutputRaw is a helper method that performs the actual signing of the
353+
// transaction.
354+
func (s *signerClient) signOutputRaw(ctx context.Context, tx *wire.MsgTx,
355+
signDescriptors []*SignDescriptor, prevOutputs []*wire.TxOut,
356+
fullDescriptor bool) ([][]byte, error) {
357+
286358
txRaw, err := encodeTx(tx)
287359
if err != nil {
288360
return nil, err
289361
}
290-
rpcSignDescs := marshallSignDescriptors(signDescriptors)
362+
rpcSignDescs := marshallSignDescriptors(signDescriptors, fullDescriptor)
291363
rpcPrevOutputs := marshallTxOut(prevOutputs)
292364

293365
rpcCtx, cancel := context.WithTimeout(ctx, s.timeout)
@@ -321,7 +393,7 @@ func (s *signerClient) ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
321393
if err != nil {
322394
return nil, err
323395
}
324-
rpcSignDescs := marshallSignDescriptors(signDescriptors)
396+
rpcSignDescs := marshallSignDescriptors(signDescriptors, false)
325397
rpcPrevOutputs := marshallTxOut(prevOutputs)
326398

327399
rpcCtx, cancel := context.WithTimeout(ctx, s.timeout)

0 commit comments

Comments
 (0)