From 37148b63dda22f1eafe535d49f1d683bcaf3bfd8 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:23:56 -0700 Subject: [PATCH 01/87] lnwire: add new Musig2Nonce type for signing+verification musig nonces --- lnwire/musig2.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 lnwire/musig2.go diff --git a/lnwire/musig2.go b/lnwire/musig2.go new file mode 100644 index 00000000000..b6a60b0449c --- /dev/null +++ b/lnwire/musig2.go @@ -0,0 +1,78 @@ +package lnwire + +import ( + "fmt" + "io" + + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // LocalNonceRecordType is the TLV type used to encode a local musig2 nonce. + LocalNonceRecordType = 2 + + // RemoteNonceRecordType is the TLV type used to encode a remote musig2 + // nonce. + RemoteNonceRecordType = 4 +) + +// Musig2Nonce represents a musig2 public nonce, which is the concatenation of +// two EC points serialized in compressed format. +type Musig2Nonce [musig2.PubNonceSize]byte + +// Record returns a TLV record that can be used to encode/decode the musig2 +// nonce from a given TLV stream. +func (m *Musig2Nonce) record(nonceType tlv.Type) tlv.Record { + return tlv.MakeStaticRecord( + nonceType, m, musig2.PubNonceSize, nonceTypeEncoder, + nonceTypeDecoder, + ) +} + +// nonceTypeEncoder is a custom TLV encoder for the Musig2Nonce type. +func nonceTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*Musig2Nonce); ok { + _, err := w.Write(v[:]) + return err + } + + fmt.Println("kek") + return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") +} + +// nonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. +func nonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + if v, ok := val.(*Musig2Nonce); ok { + _, err := io.ReadFull(r, v[:]) + return err + } + + return tlv.NewTypeForDecodingErr( + val, "lnwire.Musig2Nonce", l, musig2.PubNonceSize, + ) +} + +// LocalMusig2Nonce is a wrapper type around the Musig2Nonce type that adds the +// directional context of a "local" nonce. +// +// TODO(roasbeef): instead have single struct? +type LocalMusig2Nonce struct { + Musig2Nonce +} + +// Record returns the record to be used for encoding a local musig2 nonce. +func (l *LocalMusig2Nonce) Record() tlv.Record { + return l.record(LocalNonceRecordType) +} + +// RemoteMusig2Nonce is a wrapper type around the Musig2Nonce type that adds +// the directional context of a "remote" nonce. +type RemoteMusig2Nonce struct { + Musig2Nonce +} + +// Record returns the record to be used for encoding a remote musig2 nonce. +func (r *RemoteMusig2Nonce) Record() tlv.Record { + return r.record(RemoteNonceRecordType) +} From 8fd74597b39a1a739b4525d710096339f237f358 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:24:22 -0700 Subject: [PATCH 02/87] lnwire: add musig2 nonces to open_channel msg --- lnwallet/musig2.go | 365 +++++++++++++++++++++++++++++++++++++ lnwallet/test/btcd/kek.txt | 179 ++++++++++++++++++ lnwire/lnwire_test.go | 18 ++ lnwire/open_channel.go | 26 ++- 4 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 lnwallet/musig2.go create mode 100644 lnwallet/test/btcd/kek.txt diff --git a/lnwallet/musig2.go b/lnwallet/musig2.go new file mode 100644 index 00000000000..7804f34d3ba --- /dev/null +++ b/lnwallet/musig2.go @@ -0,0 +1,365 @@ +package lnwallet + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" +) + +// MusigPartialSig... +// +// TODO(roasbeef): move to wire package? +type MusigPartialSig struct { + sig *musig2.PartialSignature + + signerNonce [musig2.PubNonceSize]byte + + combinedNonce [musig2.PubNonceSize]byte + + signerKeys []*btcec.PublicKey +} + +// NewMusigPartialSig... +// +// TODO(roasbeef): need version that lets bind the rest later? +func NewMusigPartialSig(sig *musig2.PartialSignature, + signerNonce, combinedNonce [musig2.PubNonceSize]byte, + signerKeys []*btcec.PublicKey) *MusigPartialSig { + + return &MusigPartialSig{ + sig: sig, + signerNonce: signerNonce, + combinedNonce: combinedNonce, + signerKeys: signerKeys, + } +} + +// Serialize serializes the musig2 partial signature. The serializing includes +// the combined nonce _and_ the partial signature. The final signature is +// always 64 bytes in length. +func (p *MusigPartialSig) Serialize() []byte { + var rawSig [schnorr.SignatureSize]byte + + // For the signature, we'll encode only the x-coordinate of the + // combined nonce point. To do this we'll need to convert the R point + // in the sig to jacobian coordinate, and then extract the x-coord from + // that. + // + // TODO(roasbeef): test, or can recompute b, then arrive at the + // combined nonce, given: combinedNonce, combinedKey, msg + var nonceJ btcec.JacobianPoint + p.sig.R.AsJacobian(&nonceJ) + nonceJ.ToAffine() + + nonceX := &nonceJ.X + + nonceX.PutBytesUnchecked(rawSig[:]) + p.sig.S.PutBytesUnchecked(rawSig[32:]) + + return rawSig[:] +} + +// TODO(roasbeef): parse method, can recompute the nonce like above? + +// Verify... +func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { + var m [32]byte + copy(m[:], msg) + + // TODO(roasbeef): need diff nonce here?? + + return p.sig.Verify( + p.signerNonce, p.combinedNonce, p.signerKeys, pub, m, + musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), + ) +} + +// MusigNoncePair... +// +// TODO(roasbeef): rename to nonce1 and nonce2? +// - or signing nonce and verification nonce +type MusigNoncePair struct { + // LocalNonce... + LocalNonce *musig2.Nonces + + // RemoteNonce... + RemoteNonce *musig2.Nonces +} + +// MusigSession... +type MusigSession struct { + session *input.MuSig2SessionInfo + + combinedNonce [musig2.PubNonceSize]byte + + nonces MusigNoncePair + + nextNonces *MusigNoncePair + + // inputTxOut... + inputTxOut *wire.TxOut + + // signerKeys... + signerKeys []*btcec.PublicKey + + // remoteKey... + remoteKey *btcec.PublicKey + + // signer... + signer input.MuSig2Signer + + remoteCommit bool +} + +// NewMusigSession... +func NewMusigSession(noncePair MusigNoncePair, + localKey, remoteKey keychain.KeyDescriptor, + signer input.MuSig2Signer, inputTxOut *wire.TxOut, + remoteCommit bool) (*MusigSession, error) { + + var localNonce, remoteNonce *musig2.Nonces + + // If we're making a session for the remote commitment, then the nonce + // we use to sign is actually our _remote_ nonce, and their + // verification nonce is the local nonce. + switch { + case remoteCommit: + localNonce = noncePair.RemoteNonce + remoteNonce = noncePair.LocalNonce + + // Otherwise, we're generating a signature for our local commitment (to + // broadcast), so we'll use our normal local nonce for signing. + default: + localNonce = noncePair.LocalNonce + remoteNonce = noncePair.RemoteNonce + } + + signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} + tweakDesc := input.MuSig2Tweaks{ + TaprootBIP0086Tweak: true, + } + session, err := signer.MuSig2CreateSession( + localKey.KeyLocator, signerKeys, &tweakDesc, + [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, + musig2.WithPreGeneratedNonce(localNonce), + ) + if err != nil { + return nil, err + } + + // We'll need the raw combined nonces later to be able to verify + // partial signatures, and also combine partial signatures, so we'll + // generate it now ourselves. + combinedNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{ + noncePair.LocalNonce.PubNonce, + noncePair.RemoteNonce.PubNonce, + }) + if err != nil { + return nil, err + } + + return &MusigSession{ + nonces: noncePair, + remoteKey: remoteKey.PubKey, + session: session, + combinedNonce: combinedNonce, + inputTxOut: inputTxOut, + signerKeys: signerKeys, + signer: signer, + remoteCommit: true, + }, nil +} + +// taprootKeyspendSighash... +func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte, + value int64) ([]byte, error) { + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + pkScript, value, + ) + + sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + return txscript.CalcTaprootSignatureHash( + sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, + ) +} + +// SignCommit signs the passed commitment w/ the current signing (relative +// remote) nonce. Given nonces should only ever be used once, once the method +// returns a new nonce is returned, w/ the existing nonce blanked out. +func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.PubNonceSize]byte, error) { + // Before we can sign, we'll need to generate the sighash for their + // commitment transaction. + sigHash, err := taprootKeyspendSighash( + tx, m.inputTxOut.PkScript, m.inputTxOut.Value, + ) + if err != nil { + return nil, nil, err + } + + // Now that we have our session created, we'll use it to generate the + // initial partial signature over our sighash. + var sigHashMsg [32]byte + copy(sigHashMsg[:], sigHash) + + sig, err := m.signer.MuSig2Sign( + m.session.SessionID, sigHashMsg, false, + ) + if err != nil { + return nil, nil, err + } + + // Now that we've generated a signature with this nonce, we'll generate + // another nonce for the _next_ commitment. This'll go in the set of + // nonces for the next state, as we still need the remote party's + // verification nonce (their relative local nonce). + nextSigningNonce, err := musig2.GenNonces() + if err != nil { + return nil, nil, fmt.Errorf("unable to gen new nonce: %w", err) + } + + var nextNonces MusigNoncePair + switch { + case m.remoteCommit: + nextNonces.RemoteNonce = nextSigningNonce + default: + nextNonces.LocalNonce = nextSigningNonce + } + + m.nextNonces = &nextNonces + + return NewMusigPartialSig( + sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, + ), &nextSigningNonce.PubNonce, nil +} + +// TODO(roasbeef): re hot signatures, maybe would re-use the state less signing +// thing after all? +// +// * then able to safely generate nonce deterministically when it comes to +// signing? + +// VerifyCommitSig attempts to verify the passed partial signature against the +// passed commitment transaction. A keyspend sighash is assumed to generate the +// signed message. As we never re-use nonces, a new verification nonce (our +// relative local nonce) returned to transmit to the remote party, which allows +// them to generate another signature. +func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, + sig *musig2.PartialSignature) (*[musig2.PubNonceSize]byte, error) { + + // When we verify a commitment signature, we always assume that we're + // verifying a signature on our local commitment. Therefore, we'll use: + // their remote nonce, and also public key. + partialSig := NewMusigPartialSig( + sig, m.nonces.RemoteNonce.PubNonce, m.combinedNonce, + m.signerKeys, + ) + + // With the partial sig loaded with the proper context, we'll now + // generate the sighash that the remote party should have signed. + sigHash, err := taprootKeyspendSighash( + commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value, + ) + if err != nil { + return nil, err + } + + if !partialSig.Verify(sigHash, m.remoteKey) { + return nil, fmt.Errorf("invalid partial commit sig") + } + + // At this point, we know that their signature is valid, so we'll + // generate another verification nonce for them, so they can generate a + // new state transition. + // + // TODO(roasbeef): do this conditionally? + nextVerificationNonce, err := musig2.GenNonces() + if err != nil { + return nil, fmt.Errorf("unable to gen new nonce: %w", err) + } + + m.nextNonces = &MusigNoncePair{ + RemoteNonce: nextVerificationNonce, + } + + return &nextVerificationNonce.PubNonce, nil +} + +// MusigSessionCfg... +type MusigSessionCfg struct { + // LocalKey... + LocalKey keychain.KeyDescriptor + + // RemoteKey... + RemoteKey keychain.KeyDescriptor + + // LocalCommitNonces... + LocalCommitNonces MusigNoncePair + + // RemoteCommitNonces... + RemoteCommitNonces MusigNoncePair + + // Signer... + Signer input.MuSig2Signer + + // InputTxOut... + InputTxOut *wire.TxOut +} + +// MusigPairSession... +// +// TODO(roasbeef): split this up into two sessions? then can just make one +// later to be able to sign the txns +// +// TODO(roasbeef): chan session? +type MusigPairSession struct { + // LocalSession... + LocalSession *MusigSession + + // RemoteSession... + RemoteSession *MusigSession + + // signer... + signer input.MuSig2Signer +} + +// TODO(roasbeef): move sig here? + +// NewMusigPairSession.... +func NewMusigPairSession(cfg *MusigSessionCfg) (*MusigPairSession, error) { + // Given the config passed in, we'll now create our two sessions: one + // for the local commit, and one for the remote commit. + // + // The session for the local commit uses our local nonce and the remote + // party's remote nonce. The session for the remote commit uses our + // remote nonces, and the remote party's local nonce. + localSession, err := NewMusigSession( + cfg.LocalCommitNonces, cfg.LocalKey, cfg.RemoteKey, + cfg.Signer, cfg.InputTxOut, false, + ) + if err != nil { + return nil, err + } + remoteSession, err := NewMusigSession( + cfg.RemoteCommitNonces, cfg.LocalKey, cfg.RemoteKey, + cfg.Signer, cfg.InputTxOut, true, + ) + if err != nil { + return nil, err + } + + return &MusigPairSession{ + LocalSession: localSession, + RemoteSession: remoteSession, + signer: cfg.Signer, + }, nil +} + +// TODO(roasbeef): chan reest has a late nonce binding diff --git a/lnwallet/test/btcd/kek.txt b/lnwallet/test/btcd/kek.txt new file mode 100644 index 00000000000..e0688af9202 --- /dev/null +++ b/lnwallet/test/btcd/kek.txt @@ -0,0 +1,179 @@ +=== RUN TestLightningWallet +=== PAUSE TestLightningWallet +=== CONT TestLightningWallet +=== RUN TestLightningWallet/btcwallet/btcd:single_funding_workflow_musig2 +CHAN IS TAPROOT +CHAN IS TAPROOT +MAKING TAPROOT CHAN +pk script gen: 5120370c78e64f9cb754bd2cbcd15602bf0e4a056830c209fa7fe47c71692a0bd2d3 +MAKING TAPROOT CHAN +pk script gen: 5120370c78e64f9cb754bd2cbcd15602bf0e4a056830c209fa7fe47c71692a0bd2d3 +sign key: 02d5fc63d5e45822b926257591509d1f754e16f7d0323854d06db9794270b89173 +sign key: 02d5fc63d5e45822b926257591509d1f754e16f7d0323854d06db9794270b89173 +local nonces: (lnwallet.MusigNoncePair) { + LocalNonce: (*musig2.Nonces)(0x1400077c6c0)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 03 3a 4b 6e 1b 82 91 65 7a e0 9a a9 02 8b 26 83 |.:Kn...ez.....&.| + 00000010 86 ad 4c 30 9c 08 0e 87 5d 7b 06 c6 8e df 97 2e |..L0....]{......| + 00000020 8f 02 fd 51 7e 21 b5 27 3b 01 f9 f0 89 1d 24 5c |...Q~!.';.....$\| + 00000030 79 9c ea 03 06 01 a2 3e 1f e0 c3 8d 0d 1b 80 f1 |y......>........| + 00000040 93 fe |..| + }, + SecNonce: ([64]uint8) (len=64 cap=64) { + 00000000 f0 93 ae ff 97 99 81 2e 58 ec 35 fc 4b 24 47 ec |........X.5.K$G.| + 00000010 34 39 70 f9 1e ef 08 22 d6 23 01 ad 23 10 7f 1f |49p....".#..#...| + 00000020 ea 30 59 1d af b4 a8 72 a1 fb 4b d8 c1 b1 90 75 |.0Y....r..K....u| + 00000030 b4 54 65 2d d8 eb 9a 15 da b1 25 8f 99 77 b3 be |.Te-......%..w..| + } + }), + RemoteNonce: (*musig2.Nonces)(0x1400077ca20)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 02 99 c9 ca 38 27 3f d8 33 69 e2 0b b0 31 4b 83 |....8'?.3i...1K.| + 00000010 96 5c 0e 12 32 b7 60 dc ed 97 6c 92 22 3f e9 09 |.\..2.`...l."?..| + 00000020 32 03 7e 45 0e 89 37 38 36 2c f3 06 9f 3c 42 91 |2.~E..786,......N.3..,..| + 00000040 7d 37 |}7| + }, + SecNonce: ([64]uint8) (len=64 cap=64) { + 00000000 6b 03 cf 62 5f 32 23 65 09 8c 9c 69 f7 9e cb 1c |k..b_2#e...i....| + 00000010 ac 94 fc fd bd ad 74 77 9a 78 7c e3 40 d7 da a3 |......tw.x|.@...| + 00000020 7b c7 6c 9c 39 2d 2b 9c df 69 44 24 6f d7 31 b7 |{.l.9-+..iD$o.1.| + 00000030 94 01 71 82 b4 13 26 0a 45 e3 27 23 f3 7f 48 c7 |..q...&.E.'#..H.| + } + }), + RemoteNonce: (*musig2.Nonces)(0x1400077c750)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 02 a4 83 58 33 b7 69 cd 95 a2 e9 64 5e 5c 99 3a |...X3.i....d^\.:| + 00000010 fb ed 17 c7 09 de 8a 83 80 a1 16 33 fe 8e 25 47 |...........3..%G| + 00000020 56 03 51 ea 4f 37 fd 4d 8a 02 50 5d d6 1e 93 a0 |V.Q.O7.M..P]....| + 00000030 f0 0e 50 57 04 c3 fc 57 51 e4 6a db fd 2f 81 02 |..PW...WQ.j../..| + 00000040 a6 f3 |..| + }, + SecNonce: ([64]uint8) (len=64 cap=64) { + 00000000 6d a9 23 d0 97 aa ed 31 24 a1 53 47 91 3e 4a 22 |m.#....1$.SG.>J"| + 00000010 6c 67 0a c9 9f c2 27 5a 2c 79 72 b3 00 64 4b bf |lg....'Z,yr..dK.| + 00000020 a0 b0 92 06 27 72 c6 0e 0f 67 d1 65 61 c3 4c ae |....'r...g.ea.L.| + 00000030 a5 dc 27 19 b8 b1 52 b9 7a a7 1a a6 84 11 76 1a |..'...R.z.....v.| + } + }) +} + +sighash sign: 79eab9db688aae9241cc7018565cde5ff2d85fa4505f917dbe11d0596f634bea +sign nonce: ([66]uint8) (len=66 cap=66) { + 00000000 02 ba 81 c2 c8 db bf 80 e2 d6 68 8e c1 c4 24 38 |..........h...$8| + 00000010 33 ce 1f 6f ed 75 8c 45 52 6f a6 aa a0 a6 5d be |3..o.u.ERo....].| + 00000020 d8 02 3f 20 11 4d b1 a2 bc ee 69 f7 c1 9f 2e 48 |..? .M....i....H| + 00000030 18 49 e5 64 3e ee d9 c9 4e 84 33 ff ba 2c a9 d9 |.I.d>...N.3..,..| + 00000040 7d 37 |}7| +} + +pk script gen: 5120370c78e64f9cb754bd2cbcd15602bf0e4a056830c209fa7fe47c71692a0bd2d3 +sign key: 02e78d59cebc49c8480a408a7dce39da94d859fbc0291cba640a7de227d80b549a +sign key: 02e78d59cebc49c8480a408a7dce39da94d859fbc0291cba640a7de227d80b549a +local nonces: (lnwallet.MusigNoncePair) { + LocalNonce: (*musig2.Nonces)(0x1400077c990)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 02 ba 81 c2 c8 db bf 80 e2 d6 68 8e c1 c4 24 38 |..........h...$8| + 00000010 33 ce 1f 6f ed 75 8c 45 52 6f a6 aa a0 a6 5d be |3..o.u.ERo....].| + 00000020 d8 02 3f 20 11 4d b1 a2 bc ee 69 f7 c1 9f 2e 48 |..? .M....i....H| + 00000030 18 49 e5 64 3e ee d9 c9 4e 84 33 ff ba 2c a9 d9 |.I.d>...N.3..,..| + 00000040 7d 37 |}7| + }, + SecNonce: ([64]uint8) (len=64 cap=64) { + 00000000 6b 03 cf 62 5f 32 23 65 09 8c 9c 69 f7 9e cb 1c |k..b_2#e...i....| + 00000010 ac 94 fc fd bd ad 74 77 9a 78 7c e3 40 d7 da a3 |......tw.x|.@...| + 00000020 7b c7 6c 9c 39 2d 2b 9c df 69 44 24 6f d7 31 b7 |{.l.9-+..iD$o.1.| + 00000030 94 01 71 82 b4 13 26 0a 45 e3 27 23 f3 7f 48 c7 |..q...&.E.'#..H.| + } + }), + RemoteNonce: (*musig2.Nonces)(0x1400077c750)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 02 a4 83 58 33 b7 69 cd 95 a2 e9 64 5e 5c 99 3a |...X3.i....d^\.:| + 00000010 fb ed 17 c7 09 de 8a 83 80 a1 16 33 fe 8e 25 47 |...........3..%G| + 00000020 56 03 51 ea 4f 37 fd 4d 8a 02 50 5d d6 1e 93 a0 |V.Q.O7.M..P]....| + 00000030 f0 0e 50 57 04 c3 fc 57 51 e4 6a db fd 2f 81 02 |..PW...WQ.j../..| + 00000040 a6 f3 |..| + }, + SecNonce: ([64]uint8) (len=64 cap=64) { + 00000000 6d a9 23 d0 97 aa ed 31 24 a1 53 47 91 3e 4a 22 |m.#....1$.SG.>J"| + 00000010 6c 67 0a c9 9f c2 27 5a 2c 79 72 b3 00 64 4b bf |lg....'Z,yr..dK.| + 00000020 a0 b0 92 06 27 72 c6 0e 0f 67 d1 65 61 c3 4c ae |....'r...g.ea.L.| + 00000030 a5 dc 27 19 b8 b1 52 b9 7a a7 1a a6 84 11 76 1a |..'...R.z.....v.| + } + }) +} + +remote nonces: (lnwallet.MusigNoncePair) { + LocalNonce: (*musig2.Nonces)(0x1400077c6c0)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 03 3a 4b 6e 1b 82 91 65 7a e0 9a a9 02 8b 26 83 |.:Kn...ez.....&.| + 00000010 86 ad 4c 30 9c 08 0e 87 5d 7b 06 c6 8e df 97 2e |..L0....]{......| + 00000020 8f 02 fd 51 7e 21 b5 27 3b 01 f9 f0 89 1d 24 5c |...Q~!.';.....$\| + 00000030 79 9c ea 03 06 01 a2 3e 1f e0 c3 8d 0d 1b 80 f1 |y......>........| + 00000040 93 fe |..| + }, + SecNonce: ([64]uint8) (len=64 cap=64) { + 00000000 f0 93 ae ff 97 99 81 2e 58 ec 35 fc 4b 24 47 ec |........X.5.K$G.| + 00000010 34 39 70 f9 1e ef 08 22 d6 23 01 ad 23 10 7f 1f |49p....".#..#...| + 00000020 ea 30 59 1d af b4 a8 72 a1 fb 4b d8 c1 b1 90 75 |.0Y....r..K....u| + 00000030 b4 54 65 2d d8 eb 9a 15 da b1 25 8f 99 77 b3 be |.Te-......%..w..| + } + }), + RemoteNonce: (*musig2.Nonces)(0x1400077ca20)({ + PubNonce: ([66]uint8) (len=66 cap=66) { + 00000000 02 99 c9 ca 38 27 3f d8 33 69 e2 0b b0 31 4b 83 |....8'?.3i...1K.| + 00000010 96 5c 0e 12 32 b7 60 dc ed 97 6c 92 22 3f e9 09 |.\..2.`...l."?..| + 00000020 32 03 7e 45 0e 89 37 38 36 2c f3 06 9f 3c 42 91 |2.~E..786,... Date: Tue, 30 Aug 2022 21:24:41 -0700 Subject: [PATCH 03/87] lnwire: add musig2 nonces to accept_channel msg --- lnwire/accept_channel.go | 25 +++++++++++++++++++++++++ lnwire/lnwire_test.go | 3 +++ 2 files changed, 28 insertions(+) diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index cce1ba42ba7..16fe027889a 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -105,6 +105,16 @@ type AcceptChannel struct { // type. LeaseExpiry *LeaseExpiry + // LocalNonce is an optional field that stores a local musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + LocalNonce *LocalMusig2Nonce + + // RemoteNoncee is an optional field that stores a remote musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + RemoteNonce *RemoteMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -134,6 +144,12 @@ func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error { if a.LeaseExpiry != nil { recordProducers = append(recordProducers, a.LeaseExpiry) } + if a.LocalNonce != nil { + recordProducers = append(recordProducers, a.LocalNonce) + } + if a.RemoteNonce != nil { + recordProducers = append(recordProducers, a.RemoteNonce) + } err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err @@ -238,9 +254,12 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { var ( chanType ChannelType leaseExpiry LeaseExpiry + localNonce LocalMusig2Nonce + remoteNonce RemoteMusig2Nonce ) typeMap, err := tlvRecords.ExtractRecords( &a.UpfrontShutdownScript, &chanType, &leaseExpiry, + &localNonce, &remoteNonce, ) if err != nil { return err @@ -253,6 +272,12 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { a.LeaseExpiry = &leaseExpiry } + if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + a.LocalNonce = &localNonce + } + if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { + a.RemoteNonce = &remoteNonce + } a.ExtraData = tlvRecords diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 725934c2c93..a88edeb402a 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -528,6 +528,9 @@ func TestLightningWireProtocol(t *testing.T) { req.LeaseExpiry = new(LeaseExpiry) *req.LeaseExpiry = LeaseExpiry(1337) + + req.LocalNonce = randLocalNonce(r) + req.RemoteNonce = randRemoteNonce(r) } else { req.UpfrontShutdownScript = []byte{} } From 4c15c280d4af4628c5404e6f523eda5e44bef9f1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:25:19 -0700 Subject: [PATCH 04/87] lnwire: add musig2 nonces to funding_locked msg --- lnwire/funding_locked.go | 55 +++++++++++++++++++++++++++++++++------- lnwire/lnwire_test.go | 7 +++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/lnwire/funding_locked.go b/lnwire/funding_locked.go index fb47356bad3..776ff7295ba 100644 --- a/lnwire/funding_locked.go +++ b/lnwire/funding_locked.go @@ -27,6 +27,16 @@ type FundingLocked struct { // ShortChannelID for forwarding. AliasScid *ShortChannelID + // LocalNonce is an optional field that stores a local musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + LocalNonce *LocalMusig2Nonce + + // RemoteNonce is an optional field that stores a remote musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + RemoteNonce *RemoteMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -39,7 +49,7 @@ func NewFundingLocked(cid ChannelID, npcp *btcec.PublicKey) *FundingLocked { return &FundingLocked{ ChanID: cid, NextPerCommitmentPoint: npcp, - ExtraData: make([]byte, 0), + ExtraData: nil, } } @@ -57,16 +67,26 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { err := ReadElements(r, &c.ChanID, &c.NextPerCommitmentPoint, - &c.ExtraData, ) if err != nil { return err } + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + // Next we'll parse out the set of known records. For now, this is just // the AliasScidRecordType. - var aliasScid ShortChannelID - typeMap, err := c.ExtraData.ExtractRecords(&aliasScid) + var ( + aliasScid ShortChannelID + localNonce LocalMusig2Nonce + remoteNonce RemoteMusig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords( + &aliasScid, &localNonce, &remoteNonce, + ) if err != nil { return err } @@ -76,6 +96,16 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[AliasScidRecordType]; ok && val == nil { c.AliasScid = &aliasScid } + if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + c.LocalNonce = &localNonce + } + if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { + c.RemoteNonce = &remoteNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } return nil } @@ -95,12 +125,19 @@ func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error { } // We'll only encode the AliasScid in a TLV segment if it exists. + var recordProducers []tlv.RecordProducer if c.AliasScid != nil { - recordProducers := []tlv.RecordProducer{c.AliasScid} - err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) - if err != nil { - return err - } + recordProducers = append(recordProducers, c.AliasScid) + } + if c.LocalNonce != nil { + recordProducers = append(recordProducers, c.LocalNonce) + } + if c.RemoteNonce != nil { + recordProducers = append(recordProducers, c.RemoteNonce) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err } return WriteBytes(w, c.ExtraData) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index a88edeb402a..b7df89411c6 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -603,6 +603,13 @@ func TestLightningWireProtocol(t *testing.T) { req := NewFundingLocked(ChannelID(c), pubKey) + if r.Int31()%2 == 0 { + scid := NewShortChanIDFromInt(uint64(r.Int63())) + req.AliasScid = &scid + req.LocalNonce = randLocalNonce(r) + req.RemoteNonce = randRemoteNonce(r) + } + v[0] = reflect.ValueOf(*req) }, MsgClosingSigned: func(v []reflect.Value, r *rand.Rand) { From 4ffadd3fe3acf7557f84a8c071e23ed481e1027d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:25:48 -0700 Subject: [PATCH 05/87] lnwire: add musig2 nonces to chan_reest msg --- lnwire/channel_reestablish.go | 53 ++++++++++++++++++++++++++++++++++- lnwire/lnwire_test.go | 3 ++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go index 387cc5b580f..78819f39d1f 100644 --- a/lnwire/channel_reestablish.go +++ b/lnwire/channel_reestablish.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/tlv" ) // ChannelReestablish is a message sent between peers that have an existing @@ -62,6 +63,16 @@ type ChannelReestablish struct { // current un-revoked commitment transaction of the sending party. LocalUnrevokedCommitPoint *btcec.PublicKey + // LocalNonce is an optional field that stores a local musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + LocalNonce *LocalMusig2Nonce + + // RemoteNonce is an optional field that stores a remote musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + RemoteNonce *RemoteMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -108,6 +119,19 @@ func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error { if err := WritePublicKey(w, a.LocalUnrevokedCommitPoint); err != nil { return err } + + var recordProducers []tlv.RecordProducer + if a.LocalNonce != nil { + recordProducers = append(recordProducers, a.LocalNonce) + } + if a.RemoteNonce != nil { + recordProducers = append(recordProducers, a.RemoteNonce) + } + err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) + if err != nil { + return err + } + return WriteBytes(w, a.ExtraData) } @@ -156,7 +180,34 @@ func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error { return err } - return a.ExtraData.Decode(r) + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + localNonce LocalMusig2Nonce + remoteNonce RemoteMusig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords( + &localNonce, &remoteNonce, + ) + if err != nil { + return err + } + + if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + a.LocalNonce = &localNonce + } + if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { + a.RemoteNonce = &remoteNonce + } + + if len(tlvRecords) != 0 { + a.ExtraData = tlvRecords + } + + return nil } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index b7df89411c6..79d408b4841 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -899,6 +899,9 @@ func TestLightningWireProtocol(t *testing.T) { t.Fatalf("unable to generate key: %v", err) return } + + req.LocalNonce = randLocalNonce(r) + req.RemoteNonce = randRemoteNonce(r) } v[0] = reflect.ValueOf(req) From 82bb7650c9625687c5b11eb71c942e515964bdce Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:26:59 -0700 Subject: [PATCH 06/87] lnwire: add musig2 nonces to closing_signed --- lnwire/closing_signed.go | 44 ++++++++++++++++++++++++++++++++++++++-- lnwire/lnwire_test.go | 4 ++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index 8e11c869934..a387f0358ff 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/tlv" ) // ClosingSigned is sent by both parties to a channel once the channel is clear @@ -27,8 +28,14 @@ type ClosingSigned struct { FeeSatoshis btcutil.Amount // Signature is for the proposed channel close transaction. + // + // TODO(roasbeef): need another sig type? Signature Sig + // Musig2Nonce is the nonce the sender will use to sign the first co-op + // sign offer. + Musig2Nonce *LocalMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -55,9 +62,33 @@ var _ Message = (*ClosingSigned)(nil) // // This is part of the lnwire.Message interface. func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { - return ReadElements( - r, &c.ChannelID, &c.FeeSatoshis, &c.Signature, &c.ExtraData, + err := ReadElements( + r, &c.ChannelID, &c.FeeSatoshis, &c.Signature, ) + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + musigNonce LocalMusig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + c.Musig2Nonce = &musigNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target ClosingSigned into the passed io.Writer @@ -65,6 +96,15 @@ func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *ClosingSigned) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if c.Musig2Nonce != nil { + recordProducers = append(recordProducers, c.Musig2Nonce) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, c.ChannelID); err != nil { return err } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 79d408b4841..ef0d2e49142 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -629,6 +629,10 @@ func TestLightningWireProtocol(t *testing.T) { return } + if r.Int31()%2 == 0 { + req.Musig2Nonce = randLocalNonce(r) + } + v[0] = reflect.ValueOf(req) }, MsgCommitSig: func(v []reflect.Value, r *rand.Rand) { From fbf4a8eebb2bf9de22f8d2e69e1331c9cfc294d6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:27:42 -0700 Subject: [PATCH 07/87] lnwire: add musig2 nonces to commit_sig --- lnwire/commit_sig.go | 49 +++++++++++++++++++++++++++++++++++++++++-- lnwire/lnwire_test.go | 8 ++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go index ca105f71a66..01c9ae3dbf1 100644 --- a/lnwire/commit_sig.go +++ b/lnwire/commit_sig.go @@ -3,6 +3,8 @@ package lnwire import ( "bytes" "io" + + "github.com/lightningnetwork/lnd/tlv" ) // CommitSig is sent by either side to stage any pending HTLC's in the @@ -36,6 +38,14 @@ type CommitSig struct { // transaction should be signed. HtlcSigs []Sig + // RemoteNnoce is the "remote" nonce of the sending party, which the + // sender will use to generate a new commitment party after a + // revocation message has been sent. + // + // NOTE: This field is only populated if simple taproot channels are in + // use. + RemoteNonce *RemoteMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -58,12 +68,38 @@ var _ Message = (*CommitSig)(nil) // // This is part of the lnwire.Message interface. func (c *CommitSig) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, + err := ReadElements(r, &c.ChanID, &c.CommitSig, &c.HtlcSigs, - &c.ExtraData, ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + musigNonce RemoteMusig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { + c.RemoteNonce = &musigNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target CommitSig into the passed io.Writer @@ -71,6 +107,15 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if c.RemoteNonce != nil { + recordProducers = append(recordProducers, c.RemoteNonce) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, c.ChanID); err != nil { return err } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index ef0d2e49142..412a43969f6 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -652,7 +652,8 @@ func TestLightningWireProtocol(t *testing.T) { // Only create the slice if there will be any signatures // in it to prevent false positive test failures due to // an empty slice versus a nil slice. - numSigs := uint16(r.Int31n(1020)) + //numSigs := uint16(r.Int31n(1020)) + numSigs := uint16(r.Int31n(1019)) if numSigs > 0 { req.HtlcSigs = make([]Sig, numSigs) } @@ -664,6 +665,11 @@ func TestLightningWireProtocol(t *testing.T) { } } + // 50/50 chance to attach a remote nonce. + if r.Int31()%2 == 0 { + req.RemoteNonce = randRemoteNonce(r) + } + v[0] = reflect.ValueOf(*req) }, MsgRevokeAndAck: func(v []reflect.Value, r *rand.Rand) { From 0ccaaa02f0c5e2d07b7a7b5328a7a5e37aebcd1e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:28:01 -0700 Subject: [PATCH 08/87] lnwire: add musig2 nonces to revoke_and_ack --- lnwire/lnwire_test.go | 5 +++++ lnwire/revoke_and_ack.go | 45 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 412a43969f6..6bab4e848da 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -689,6 +689,11 @@ func TestLightningWireProtocol(t *testing.T) { return } + // 50/50 chance to attach a local nonce. + if r.Int31()%2 == 0 { + req.LocalNonce = randLocalNonce(r) + } + v[0] = reflect.ValueOf(*req) }, MsgChannelAnnouncement: func(v []reflect.Value, r *rand.Rand) { diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go index bdc06d2fe43..6edcc324e7d 100644 --- a/lnwire/revoke_and_ack.go +++ b/lnwire/revoke_and_ack.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/tlv" ) // RevokeAndAck is sent by either side once a CommitSig message has been @@ -32,6 +33,11 @@ type RevokeAndAck struct { // transaction. NextRevocationKey *btcec.PublicKey + // LocalNonce is the next _local_ nonce for the sending party. This + // allows the receiving party to propose a new commitment using their + // remote nonce and the sender's local nonce. + LocalNonce *LocalMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -54,12 +60,38 @@ var _ Message = (*RevokeAndAck)(nil) // // This is part of the lnwire.Message interface. func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, + err := ReadElements(r, &c.ChanID, c.Revocation[:], &c.NextRevocationKey, - &c.ExtraData, ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + musigNonce LocalMusig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + c.LocalNonce = &musigNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target RevokeAndAck into the passed io.Writer @@ -67,6 +99,15 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *RevokeAndAck) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if c.LocalNonce != nil { + recordProducers = append(recordProducers, c.LocalNonce) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, c.ChanID); err != nil { return err } From cbe0a9c4feb1a5d434cf83e42e512db0c64adbe5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:28:15 -0700 Subject: [PATCH 09/87] lnwire: add musig2 nonces to shutdown --- lnwire/lnwire_test.go | 26 +++++++++++++++++++++++++ lnwire/shutdown.go | 44 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 6bab4e848da..ff098d50c20 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -612,6 +612,32 @@ func TestLightningWireProtocol(t *testing.T) { v[0] = reflect.ValueOf(*req) }, + MsgShutdown: func(v []reflect.Value, r *rand.Rand) { + var c [32]byte + _, err := r.Read(c[:]) + if err != nil { + t.Fatalf("unable to generate chan id: %v", err) + return + } + + shutdownAddr, err := randDeliveryAddress(r) + if err != nil { + t.Fatalf("unable to generate delivery address: %v", err) + return + } + + req := Shutdown{ + ChannelID: ChannelID(c), + Address: shutdownAddr, + ExtraData: make([]byte, 0), + } + + if r.Int31()%2 == 0 { + req.Musig2Nonce = randLocalNonce(r) + } + + v[0] = reflect.ValueOf(req) + }, MsgClosingSigned: func(v []reflect.Value, r *rand.Rand) { req := ClosingSigned{ FeeSatoshis: btcutil.Amount(r.Int63()), diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index 2adb6a082df..87a2b882f76 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -3,6 +3,8 @@ package lnwire import ( "bytes" "io" + + "github.com/lightningnetwork/lnd/tlv" ) // Shutdown is sent by either side in order to initiate the cooperative closure @@ -17,6 +19,10 @@ type Shutdown struct { // Address is the script to which the channel funds will be paid. Address DeliveryAddress + // Musig2Nonce is the nonce the sender will use to sign the first co-op + // sign offer. + Musig2Nonce *LocalMusig2Nonce + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -40,7 +46,34 @@ var _ Message = (*Shutdown)(nil) // // This is part of the lnwire.Message interface. func (s *Shutdown) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, &s.ChannelID, &s.Address, &s.ExtraData) + err := ReadElements(r, &s.ChannelID, &s.Address) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + musigNonce LocalMusig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + s.Musig2Nonce = &musigNonce + } + + if len(tlvRecords) != 0 { + s.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target Shutdown into the passed io.Writer observing @@ -48,6 +81,15 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if s.Musig2Nonce != nil { + recordProducers = append(recordProducers, s.Musig2Nonce) + } + err := EncodeMessageExtraData(&s.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, s.ChannelID); err != nil { return err } From 090cf5284342a319435a7440e925e21f3681c64d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:28:30 -0700 Subject: [PATCH 10/87] lnwire: add feature bits for simple-taproot-chans --- lnwire/features.go | 10 ++++++++++ lnwire/funding_signed.go | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/lnwire/features.go b/lnwire/features.go index c386fd37be6..193936ed177 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -221,6 +221,14 @@ const ( // TODO: Decide on actual feature bit value. ScriptEnforcedLeaseOptional FeatureBit = 2023 + // SimpleTaprootChannelsRequred is an required bit that indicates the + // node is able to create unadvertised taproot-native channels. + SimpleTaprootChannelsRequired = 80 + + // SimpleTaprootChannelsOptional is an optional bit that indicates the + // node is able to create unadvertised taproot-native channels. + SimpleTaprootChannelsOptional = 81 + // maxAllowedSize is a maximum allowed size of feature vector. // // NOTE: Within the protocol, the maximum allowed message size is 65535 @@ -280,6 +288,8 @@ var Features = map[FeatureBit]string{ ZeroConfOptional: "zero-conf", ShutdownAnySegwitRequired: "shutdown-any-segwit", ShutdownAnySegwitOptional: "shutdown-any-segwit", + SimpleTaprootChannelsRequired: "simple-taproot-chans", + SimpleTaprootChannelsOptional: "simple-taproot-chans", } // RawFeatureVector represents a set of feature bits as defined in BOLT-09. A diff --git a/lnwire/funding_signed.go b/lnwire/funding_signed.go index d7386f2ed0f..5af5f0d5ca5 100644 --- a/lnwire/funding_signed.go +++ b/lnwire/funding_signed.go @@ -15,6 +15,10 @@ type FundingSigned struct { // CommitSig is Bob's signature for Alice's version of the commitment // transaction. + // + // TODO(roasbeef): schnorr sigs have a diff encoding... + // * need to make this a type param instead? + // * or interface to wrap the structure? CommitSig Sig // ExtraData is the set of data that was appended to this message to From 4ffec3c44a130acfc0950d17921ec972c37bc142 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:28:50 -0700 Subject: [PATCH 11/87] lnwire: always sort records in ExtractRecords This matches the behavior when we encode TLV records. --- lnwire/extra_bytes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lnwire/extra_bytes.go b/lnwire/extra_bytes.go index 88b914c384b..8a0a95cd189 100644 --- a/lnwire/extra_bytes.go +++ b/lnwire/extra_bytes.go @@ -90,6 +90,10 @@ func (e *ExtraOpaqueData) ExtractRecords(recordProducers ...tlv.RecordProducer) records = append(records, producer.Record()) } + // Ensure that the set of records are sorted before we attempt to + // decode from the stream, to ensure they're canonical. + tlv.SortRecords(records) + extraBytesReader := bytes.NewReader(*e) tlvStream, err := tlv.NewStream(records...) From 15552f746f22eb2098104d4f1aadd1b42c955c13 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:29:24 -0700 Subject: [PATCH 12/87] feature: define default set and deps for simple taproot chans --- feature/default_sets.go | 4 ++++ feature/deps.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/feature/default_sets.go b/feature/default_sets.go index 1b1fd1f104a..cefa1c0bc42 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -83,4 +83,8 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, + lnwire.SimpleTaprootChannelsOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N + }, } diff --git a/feature/deps.go b/feature/deps.go index 8e1d8ac095c..5b8ad964707 100644 --- a/feature/deps.go +++ b/feature/deps.go @@ -75,6 +75,9 @@ var deps = depDesc{ lnwire.ZeroConfOptional: { lnwire.ScidAliasOptional: {}, }, + lnwire.SimpleTaprootChannelsOptional: { + lnwire.ExplicitChannelTypeOptional: {}, + }, } // ValidateDeps asserts that a feature vector sets all features and their From c45f6665a9158870ce84df3a2527137ffa49560c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:29:39 -0700 Subject: [PATCH 13/87] channeldb: add SimpleTaprootFeatureBit chan type --- channeldb/channel.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index 8c04ccdc226..175ef92668f 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -278,6 +278,10 @@ const ( // ScidAliasFeatureBit indicates that the scid-alias feature bit was // negotiated during the lifetime of this channel. ScidAliasFeatureBit ChannelType = 1 << 9 + + // SimpleTaprootFeatureBit indicates that the simple-taproot-channels + // feature bit was negotiated during the lifetime of the channel. + SimpleTaprootFeatureBit ChannelType = 1 << 10 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -343,6 +347,11 @@ func (c ChannelType) HasScidAliasFeature() bool { return c&ScidAliasFeatureBit == ScidAliasFeatureBit } +// IsTaproot returns true if the channel is using taproot features. +func (c ChannelType) IsTaproot() bool { + return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit +} + // ChannelConstraints represents a set of constraints meant to allow a node to // limit their exposure, enact flow control and ensure that all HTLCs are // economically relevant. This struct will be mirrored for both sides of the @@ -1315,11 +1324,11 @@ func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) { // commitment chains in the case of a last or only partially processed message. // When the remote party receives this message one of three things may happen: // -// 1. We're fully synced and no messages need to be sent. -// 2. We didn't get the last CommitSig message they sent, so they'll re-send -// it. -// 3. We didn't get the last RevokeAndAck message they sent, so they'll -// re-send it. +// 1. We're fully synced and no messages need to be sent. +// 2. We didn't get the last CommitSig message they sent, so they'll re-send +// it. +// 3. We didn't get the last RevokeAndAck message they sent, so they'll +// re-send it. // // If this is a restored channel, having status ChanStatusRestored, then we'll // modify our typical chan sync message to ensure they force close even if From 4fa23726e09db4fbc2a0056e49280019c8df0c57 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:30:15 -0700 Subject: [PATCH 14/87] funding: define accepted feature bit combos for taproot chans --- funding/commitment_type_negotiation.go | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go index 80d5be904a6..66d1012c92a 100644 --- a/funding/commitment_type_negotiation.go +++ b/funding/commitment_type_negotiation.go @@ -220,6 +220,51 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, } return lnwallet.CommitmentTypeTweakless, nil + // Simple taproot channels only. + case channelFeatures.OnlyContains(lnwire.SimpleTaprootChannelsRequired): + + if !hasFeatures( + local, remote, lnwire.SimpleTaprootChannelsOptional, + ) { + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaproot, nil + + // Simple taproot channels with scid only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootChannelsRequired, + lnwire.ScidAliasRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootChannelsOptional, + lnwire.ScidAliasOptional, + ) { + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaproot, nil + + // Simple taproot channels with zero conf only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootChannelsRequired, + lnwire.ScidAliasRequired, + lnwire.ZeroConfRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootChannelsOptional, + lnwire.ScidAliasOptional, + lnwire.ZeroConfOptional, + ) { + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaproot, nil + // No features, use legacy commitment type. case channelFeatures.IsEmpty(): return lnwallet.CommitmentTypeLegacy, nil From a318d4de68d6d09f41a1d345c0da33313060fc81 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:31:42 -0700 Subject: [PATCH 15/87] input: thread through musig2.SessionOptions for create session This'll let us specify our _own_ nonce during the process. We'll need to do this for channel funding, and also to drive the state machine forward as well. --- input/musig2.go | 13 +++++++++++-- input/test_utils.go | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/input/musig2.go b/input/musig2.go index 2fc689d7745..4bcc099de63 100644 --- a/input/musig2.go +++ b/input/musig2.go @@ -31,9 +31,16 @@ type MuSig2Signer interface { // public key of the local signing key. If nonces of other parties are // already known, they can be submitted as well to reduce the number of // method calls necessary later on. + // + // The set of sessionOpts are _optional_ and allow a caller to modify the + // generated sessions. As an example the local nonce might already be generated + // ahead of time. MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey, - *MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, - error) + *MuSig2Tweaks, [][musig2.PubNonceSize]byte, + ...musig2.SessionOption) (*MuSig2SessionInfo, error) + + // TODO(roasbeef): need to make the sparse one here? + // * don't have any info but want a session // MuSig2RegisterNonces registers one or more public nonces of other // signing participants for a session identified by its ID. This method @@ -76,6 +83,8 @@ type MuSig2SessionInfo struct { // CombinedKey is the combined public key with all tweaks applied to it. CombinedKey *btcec.PublicKey + // TODO(roasbeef): also add combined nonce + // TaprootTweak indicates whether a taproot tweak (BIP-0086 or script // path) was used. The TaprootInternalKey will only be set if this is // set to true. diff --git a/input/test_utils.go b/input/test_utils.go index 99b8e050fd8..015bfd9530d 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -141,7 +141,8 @@ func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor // submitted as well to reduce the number of method calls necessary later on. func (m *MockSigner) MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey, *MuSig2Tweaks, - [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error) { + [][musig2.PubNonceSize]byte, + ...musig2.SessionOption) (*MuSig2SessionInfo, error) { return nil, nil } From 75f4a1489bd16804b8ad72a9e9d459eddf3fdce1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:32:00 -0700 Subject: [PATCH 16/87] input: add GenTaprootFundingScript for musig2 chans --- input/script_utils.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/input/script_utils.go b/input/script_utils.go index e12b57c43af..832e4af915a 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -141,6 +141,40 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro return witnessScript, wire.NewTxOut(amt, pkScript), nil } +// GenTaprootFundingScript constructs the taproot-native funding output that +// uses musig2 to create a single aggregated key to anchor the channel. +func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey, + amt int64) ([]byte, *wire.TxOut, error) { + + // Similar to the existing p2wsh funding script, we'll always make sure + // we sort the keys before any major operations. In order to ensure + // that there's no other way this output can be spent, we'll use a BIP + // 86 tweak here during aggregation. + // + // TODO(roasbeef): revisit if BIP 86 is needed here? + combinedKey, _, _, err := musig2.AggregateKeys( + []*btcec.PublicKey{aPub, bPub}, true, + musig2.WithBIP86KeyTweak(), + ) + if err != nil { + return nil, nil, fmt.Errorf("unable to combine keys: %w", err) + } + + // Now that we have the combined key, we can create a taproot pkScript + // from this, and then make the txout given the amount. + pkScript, err := PayToTaprootScript(combinedKey.FinalKey) + if err != nil { + return nil, nil, fmt.Errorf("unable to make taproot "+ + "pkscript: %w", err) + } + + txOut := wire.NewTxOut(amt, pkScript) + + // For the "witness program" we just return the raw pkScript since the + // output we create can _only_ be spent with a musig2 signature. + return pkScript, txOut, nil +} + // SpendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh // multi-sig output. func SpendMultiSig(witnessScript, pubA []byte, sigA Signature, From 5d40959468fc0d9509f182080bf9db0466480d4a Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:33:08 -0700 Subject: [PATCH 17/87] input: add TaprootCommitScriptToSelf for local self balance --- input/script_utils.go | 224 ++++++++++++++++++++++++++++-------------- 1 file changed, 152 insertions(+), 72 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index 832e4af915a..408aac4c151 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -237,35 +237,40 @@ func Ripemd160H(d []byte) []byte { // output payment for the sender's version of the commitment transaction. The // possible script paths from this output include: // -// * The sender timing out the HTLC using the second level HTLC timeout -// transaction. -// * The receiver of the HTLC claiming the output on-chain with the payment -// preimage. -// * The receiver of the HTLC sweeping all the funds in the case that a -// revoked commitment transaction bearing this HTLC was broadcast. +// - The sender timing out the HTLC using the second level HTLC timeout +// transaction. +// - The receiver of the HTLC claiming the output on-chain with the payment +// preimage. +// - The receiver of the HTLC sweeping all the funds in the case that a +// revoked commitment transaction bearing this HTLC was broadcast. // // If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation // cases, to allow sweeping only after confirmation. // // Possible Input Scripts: -// SENDR: <0> <0> (spend using HTLC timeout transaction) -// RECVR: -// REVOK: -// * receiver revoke +// +// SENDR: <0> <0> (spend using HTLC timeout transaction) +// RECVR: +// REVOK: +// * receiver revoke // // OP_DUP OP_HASH160 OP_EQUAL // OP_IF -// OP_CHECKSIG +// +// OP_CHECKSIG +// // OP_ELSE -// -// OP_SWAP OP_SIZE 32 OP_EQUAL -// OP_NOTIF -// OP_DROP 2 OP_SWAP 2 OP_CHECKMULTISIG -// OP_ELSE -// OP_HASH160 OP_EQUALVERIFY -// OP_CHECKSIG -// OP_ENDIF -// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// +// +// OP_SWAP OP_SIZE 32 OP_EQUAL +// OP_NOTIF +// OP_DROP 2 OP_SWAP 2 OP_CHECKMULTISIG +// OP_ELSE +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIG +// OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// // OP_ENDIF func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, paymentHash []byte, @@ -476,36 +481,40 @@ func SenderHtlcSpendTimeout(receiverSig Signature, // ReceiverHTLCScript constructs the public key script for an incoming HTLC // output payment for the receiver's version of the commitment transaction. The // possible execution paths from this script include: -// * The receiver of the HTLC uses its second level HTLC transaction to +// - The receiver of the HTLC uses its second level HTLC transaction to // advance the state of the HTLC into the delay+claim state. -// * The sender of the HTLC sweeps all the funds of the HTLC as a breached +// - The sender of the HTLC sweeps all the funds of the HTLC as a breached // commitment was broadcast. -// * The sender of the HTLC sweeps the HTLC on-chain after the timeout period +// - The sender of the HTLC sweeps the HTLC on-chain after the timeout period // of the HTLC has passed. // // If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation // cases, to allow sweeping only after confirmation. // // Possible Input Scripts: -// RECVR: <0> (spend using HTLC success transaction) -// REVOK: -// SENDR: 0 // +// RECVR: <0> (spend using HTLC success transaction) +// REVOK: +// SENDR: 0 // // OP_DUP OP_HASH160 OP_EQUAL // OP_IF -// OP_CHECKSIG +// +// OP_CHECKSIG +// // OP_ELSE -// -// OP_SWAP OP_SIZE 32 OP_EQUAL -// OP_IF -// OP_HASH160 OP_EQUALVERIFY -// 2 OP_SWAP 2 OP_CHECKMULTISIG -// OP_ELSE -// OP_DROP OP_CHECKLOCKTIMEVERIFY OP_DROP -// OP_CHECKSIG -// OP_ENDIF -// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// +// +// OP_SWAP OP_SIZE 32 OP_EQUAL +// OP_IF +// OP_HASH160 OP_EQUALVERIFY +// 2 OP_SWAP 2 OP_CHECKMULTISIG +// OP_ELSE +// OP_DROP OP_CHECKLOCKTIMEVERIFY OP_DROP +// OP_CHECKSIG +// OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// // OP_ENDIF func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, @@ -748,26 +757,33 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor, // spent in a particular way, and to a particular output. // // Possible Input Scripts: -// * To revoke an HTLC output that has been transitioned to the claim+delay -// state: -// * 1 // -// * To claim and HTLC output, either with a pre-image or due to a timeout: -// * 0 +// - To revoke an HTLC output that has been transitioned to the claim+delay +// state: +// +// - 1 +// +// - To claim and HTLC output, either with a pre-image or due to a timeout: +// +// - 0 // // OP_IF -// +// +// +// // OP_ELSE -// -// OP_CHECKSEQUENCEVERIFY -// OP_DROP -// +// +// +// OP_CHECKSEQUENCEVERIFY +// OP_DROP +// +// // OP_ENDIF // OP_CHECKSIG // // TODO(roasbeef): possible renames for second-level -// * transition? -// * covenant output +// - transition? +// - covenant output func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, csvDelay uint32) ([]byte, error) { @@ -814,23 +830,30 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, // spent in a particular way, and to a particular output. // // Possible Input Scripts: -// * To revoke an HTLC output that has been transitioned to the claim+delay -// state: -// * 1 // -// * To claim an HTLC output, either with a pre-image or due to a timeout: -// * 0 +// - To revoke an HTLC output that has been transitioned to the claim+delay +// state: +// +// - 1 +// +// - To claim an HTLC output, either with a pre-image or due to a timeout: +// +// - 0 // // OP_IF -// +// +// +// // OP_ELSE -// -// OP_CHECKLOCKTIMEVERIFY -// OP_DROP -// -// OP_CHECKSEQUENCEVERIFY -// OP_DROP -// +// +// +// OP_CHECKLOCKTIMEVERIFY +// OP_DROP +// +// OP_CHECKSEQUENCEVERIFY +// OP_DROP +// +// // OP_ENDIF // OP_CHECKSIG. func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, @@ -978,7 +1001,7 @@ func HtlcSecondLevelSpend(signer Signer, signDesc *SignDescriptor, // LockTimeToSequence converts the passed relative locktime to a sequence // number in accordance to BIP-68. // See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki -// * (Compatibility) +// - (Compatibility) func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 { if !isSeconds { // The locktime is to be expressed in confirmations. @@ -998,17 +1021,19 @@ func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 { // can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: -// REVOKE: 1 -// SENDRSWEEP: +// +// REVOKE: 1 +// SENDRSWEEP: // // Output Script: -// OP_IF -// -// OP_ELSE -// OP_CHECKSEQUENCEVERIFY OP_DROP -// -// OP_ENDIF -// OP_CHECKSIG +// +// OP_IF +// +// OP_ELSE +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// OP_ENDIF +// OP_CHECKSIG func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) ([]byte, error) { // This script is spendable under two conditions: either the // 'csvTimeout' has passed and we can redeem our funds, or they can @@ -1043,6 +1068,61 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) return builder.Script() } +// TaprootCommitScriptToSelf creates the taproot witness program that commits +// to the revocation (keyspend) and delay path (script path) in a single +// taproot output key. +// +// For the delay path we have the following tapscript leaf script: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// This can then be spent with just: +// +// +// +// Where the to_delay_script is listed above, and the delay_control_block +// computed as: +// +// delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey +// +// The revocation key spend path will simply present a valid signature with the +// witness being just: +// +// +func TaprootCommitScriptToSelf(csvTimeout uint32, + selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) { + + // First, we'll need to construct the tapLeaf that'll be our delay CSV + // clause. + // + // TODO(roasbeef): extract into diff func + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(selfKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_DROP) + + delayScript, err := builder.Script() + if err != nil { + return nil, err + } + + // With the delay script computed, we'll now create a tapscript tree + // with a single leaf, and then obtain a root from that. + tapLeaf := txscript.NewBaseTapLeaf(delayScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // Now that we have our root, we can arrive at the final output script + // by tweaking the internal key with this root. + toLocalOutputKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return toLocalOutputKey, nil +} + // LeaseCommitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. // If the other party learns of the preimage to the revocation hash, then they From b459a813b85ea4d4608db940da2b0e366ce7d9f9 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:33:29 -0700 Subject: [PATCH 18/87] input: add TaprootCommitScriptToRemote for remote taproot outputs --- input/script_utils.go | 74 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index 408aac4c151..ac3a9ddfb0f 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -1129,18 +1129,20 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, // can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: -// REVOKE: 1 -// SENDRSWEEP: +// +// REVOKE: 1 +// SENDRSWEEP: // // Output Script: -// OP_IF -// -// OP_ELSE -// OP_CHECKLOCKTIMEVERIFY OP_DROP -// OP_CHECKSEQUENCEVERIFY OP_DROP -// -// OP_ENDIF -// OP_CHECKSIG +// +// OP_IF +// +// OP_ELSE +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// OP_ENDIF +// OP_CHECKSIG func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, csvTimeout, leaseExpiry uint32) ([]byte, error) { @@ -1308,9 +1310,11 @@ func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { // transaction. The money can only be spend after one confirmation. // // Possible Input Scripts: -// SWEEP: +// +// SWEEP: // // Output Script: +// // OP_CHECKSIGVERIFY // 1 OP_CHECKSEQUENCEVERIFY func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { @@ -1327,6 +1331,54 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } +// TaprootCommitScriptToRemote constructs a taproot witness program for the +// output on the commitment transaction for the remote party. For the top level +// key spend, we'll use the combined funding key (musig2.KeyAgg(k1, k2)), as a +// sort of practical NUMs point (the local party would never sign for this). We +// then commit to a single tapscript leaf that holds the normal CSV 1 delay +// script. +// +// Our single tapleaf will use the following script: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// +// The CSV clause is a bit subtle, but OP_CHECKSIG will return true if it +// succeeds, which then enforces our 1 CSV. The true will remain on the stack, +// causing the script to pass. If the CHECKSIG fails, then a 0 will remain on +// the stack. +// +// TODO(roasbeef): double check here can't pass additional stack elements? +func TaprootCommitScriptToRemote(combinedFundingKey, + remoteKey *btcec.PublicKey) (*btcec.PublicKey, error) { + + // First, construct the remote party's tapscript they'll use to sweep their + // outputs. + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(remoteKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + delayScript, err := builder.Script() + if err != nil { + return nil, err + } + + // With this script constructed, we'll map that into a tapLeaf, then + // make a new tapscript root from that. + tapLeaf := txscript.NewBaseTapLeaf(delayScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // Now that we have our root, we can arrive at the final output script + // by tweaking the internal key with this root. + toRemoteOutputKey := txscript.ComputeTaprootOutputKey( + remoteKey, tapScriptRoot[:], + ) + + return toRemoteOutputKey, nil +} + // LeaseCommitScriptToRemoteConfirmed constructs the script for the output on // the commitment transaction paying to the remote party of said commitment // transaction. The money can only be spend after one confirmation. From 9717efd42fddc2ea8c7c6fa3c0e3210d7110c685 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:33:49 -0700 Subject: [PATCH 19/87] input: add TaprootOutputKeyAnchor for taproot anchor outputs --- input/script_utils.go | 88 ++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index ac3a9ddfb0f..7033aa5e71a 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -1384,12 +1386,14 @@ func TaprootCommitScriptToRemote(combinedFundingKey, // transaction. The money can only be spend after one confirmation. // // Possible Input Scripts: -// SWEEP: +// +// SWEEP: // // Output Script: -// OP_CHECKSIGVERIFY -// OP_CHECKLOCKTIMEVERIFY OP_DROP -// 1 OP_CHECKSEQUENCEVERIFY +// +// OP_CHECKSIGVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// 1 OP_CHECKSEQUENCEVERIFY func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey, leaseExpiry uint32) ([]byte, error) { @@ -1444,10 +1448,12 @@ func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor, // the given key immediately, or by anyone after 16 confirmations. // // Possible Input Scripts: -// By owner: -// By anyone (after 16 conf): +// +// By owner: +// By anyone (after 16 conf): // // Output Script: +// // OP_CHECKSIG OP_IFDUP // OP_NOTIF // OP_16 OP_CSV @@ -1471,6 +1477,41 @@ func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } +// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that +// encodes the anchor output spending conditions: the passed key can be used +// for keyspend, with the OP_CSV 16 clause living within an internal tapscript +// leaf. +// +// Spend paths: +// - Key spend: +// - Script spend: OP_16 CSV +func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { + // The main script used is just a OP_16 CSV (anyone can sweep after 16 + // blocks). + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_16) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + anchorScript, err := builder.Script() + if err != nil { + return nil, err + } + + // With the script, we can make our sole leaf, then derive the root + // from that. + tapLeaf := txscript.NewBaseTapLeaf(anchorScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // Now that we have our root, we can arrive at the final output script + // by tweaking the internal key with this root. + anchorKey := txscript.ComputeTaprootOutputKey( + key, tapScriptRoot[:], + ) + + return anchorKey, nil +} + // CommitSpendAnchor constructs a valid witness allowing a node to spend their // anchor output on the commitment transaction using their funding key. This is // used for the anchor channel type. @@ -1514,7 +1555,7 @@ func CommitSpendAnchorAnyone(script []byte) (wire.TxWitness, error) { // the pay/delay base point. The end end results is that the basePoint is // tweaked as follows: // -// * key = basePoint + sha256(commitPoint || basePoint)*G +// - key = basePoint + sha256(commitPoint || basePoint)*G func SingleTweakBytes(commitPoint, basePoint *btcec.PublicKey) []byte { h := sha256.New() h.Write(commitPoint.SerializeCompressed()) @@ -1529,15 +1570,15 @@ func SingleTweakBytes(commitPoint, basePoint *btcec.PublicKey) []byte { // The opposite applies for when tweaking remote keys. Precisely, the following // operation is used to "tweak" public keys: // -// tweakPub := basePoint + sha256(commitPoint || basePoint) * G -// := G*k + sha256(commitPoint || basePoint)*G -// := G*(k + sha256(commitPoint || basePoint)) +// tweakPub := basePoint + sha256(commitPoint || basePoint) * G +// := G*k + sha256(commitPoint || basePoint)*G +// := G*(k + sha256(commitPoint || basePoint)) // // Therefore, if a party possess the value k, the private key of the base // point, then they are able to derive the proper private key for the // revokeKey by computing: // -// revokePriv := k + sha256(commitPoint || basePoint) mod N +// revokePriv := k + sha256(commitPoint || basePoint) mod N // // Where N is the order of the sub-group. // @@ -1581,7 +1622,7 @@ func TweakPubKeyWithTweak(pubKey *btcec.PublicKey, // revoked state. Precisely, the following operation is used to derive a // tweaked private key: // -// * tweakPriv := basePriv + sha256(commitment || basePub) mod N +// - tweakPriv := basePriv + sha256(commitment || basePub) mod N // // Where N is the order of the sub-group. func TweakPrivKey(basePriv *btcec.PrivateKey, @@ -1602,24 +1643,24 @@ func TweakPrivKey(basePriv *btcec.PrivateKey, // revoked commitment transaction, then if the other party knows the revocation // preimage, then they'll be able to derive the corresponding private key to // this private key by exploiting the homomorphism in the elliptic curve group: -// * https://en.wikipedia.org/wiki/Group_homomorphism#Homomorphisms_of_abelian_groups +// - https://en.wikipedia.org/wiki/Group_homomorphism#Homomorphisms_of_abelian_groups // // The derivation is performed as follows: // -// revokeKey := revokeBase * sha256(revocationBase || commitPoint) + -// commitPoint * sha256(commitPoint || revocationBase) +// revokeKey := revokeBase * sha256(revocationBase || commitPoint) + +// commitPoint * sha256(commitPoint || revocationBase) // -// := G*(revokeBasePriv * sha256(revocationBase || commitPoint)) + -// G*(commitSecret * sha256(commitPoint || revocationBase)) +// := G*(revokeBasePriv * sha256(revocationBase || commitPoint)) + +// G*(commitSecret * sha256(commitPoint || revocationBase)) // -// := G*(revokeBasePriv * sha256(revocationBase || commitPoint) + -// commitSecret * sha256(commitPoint || revocationBase)) +// := G*(revokeBasePriv * sha256(revocationBase || commitPoint) + +// commitSecret * sha256(commitPoint || revocationBase)) // // Therefore, once we divulge the revocation secret, the remote peer is able to // compute the proper private key for the revokeKey by computing: // -// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + -// (commitSecret * sha256(commitPoint || revocationBase)) mod N +// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + +// (commitSecret * sha256(commitPoint || revocationBase)) mod N // // Where N is the order of the sub-group. func DeriveRevocationPubkey(revokeBase, @@ -1671,8 +1712,9 @@ func DeriveRevocationPubkey(revokeBase, // a previously revoked commitment transaction. // // The private key is derived as follows: -// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + -// (commitSecret * sha256(commitPoint || revocationBase)) mod N +// +// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + +// (commitSecret * sha256(commitPoint || revocationBase)) mod N // // Where N is the order of the sub-group. func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey, From cc2fa3c3d0622f3ffd7a0cbb67e08b0210b49c11 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:34:02 -0700 Subject: [PATCH 20/87] input: create new PayToTaprootScript utility func --- input/taproot.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/input/taproot.go b/input/taproot.go index 0319228badb..8008179d310 100644 --- a/input/taproot.go +++ b/input/taproot.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" @@ -105,3 +106,15 @@ func TapscriptPartialReveal(internalKey *btcec.PublicKey, RevealedScript: revealedLeaf.Script, } } + +// PayToTaprootScript creates a new script to pay to a version 1 +// (taproot) witness program. The passed public key will be serialized as an +// x-only key to create the witness program. +func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { + builder := txscript.NewScriptBuilder() + + builder.AddOp(txscript.OP_1) + builder.AddData(schnorr.SerializePubKey(taprootKey)) + + return builder.Script() +} From a2cd14b875d66aa146504bedd14cb3b28ddffac1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:34:28 -0700 Subject: [PATCH 21/87] lnwallet/btcwallet: update musig2 signer to pass in session opts --- lnwallet/btcwallet/signer.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index 91ee53e80e7..cdb1887dc78 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -478,10 +478,14 @@ type muSig2State struct { // all signing parties must be provided, including the public key of the local // signing key. If nonces of other parties are already known, they can be // submitted as well to reduce the number of method calls necessary later on. +// +// The set of sessionOpts are _optional_ and allow a caller to modify the +// generated sessions. As an example the local nonce might already be generated +// ahead of time. func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator, allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, - otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, - error) { + otherSignerNonces [][musig2.PubNonceSize]byte, + sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { // We need to derive the private key for signing. In the remote signing // setup, this whole RPC call will be forwarded to the signing @@ -507,12 +511,15 @@ func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator, } // The session keeps track of the own and other nonces. - musigSession, err := musigContext.NewSession() + musigSession, err := musigContext.NewSession(sessionOpts...) if err != nil { return nil, fmt.Errorf("error creating MuSig2 signing "+ "session: %v", err) } + // TODO(roasbeef): actually want to expose the context for channels so + // don't always need to re-create? + // Add all nonces we might've learned so far. haveAllNonces := false for _, otherSignerNonce := range otherSignerNonces { From f0360c9f8287f65996c248e912050ec04d52a57e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:35:27 -0700 Subject: [PATCH 22/87] lnwallet: add new taproot commitment type enum value --- lnwallet/reservation.go | 44 +++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index ba9fc76c6b3..65a6255900b 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -42,6 +42,11 @@ const ( // guarantee that the channel initiator has no incentives to close a // leased channel before its maturity date. CommitmentTypeScriptEnforcedLease + + // CommitmentTypeSimpleTaproot is the base commitment type for the + // channels that use a musig2 funding output and the tapscript tree + // where relevant for the commitment transaciton pk scripts. + CommitmentTypeSimpleTaproot ) // HasStaticRemoteKey returns whether the commitment type supports remote @@ -50,7 +55,8 @@ func (c CommitmentType) HasStaticRemoteKey() bool { switch c { case CommitmentTypeTweakless, CommitmentTypeAnchorsZeroFeeHtlcTx, - CommitmentTypeScriptEnforcedLease: + CommitmentTypeScriptEnforcedLease, + CommitmentTypeSimpleTaproot: return true default: return false @@ -61,13 +67,19 @@ func (c CommitmentType) HasStaticRemoteKey() bool { func (c CommitmentType) HasAnchors() bool { switch c { case CommitmentTypeAnchorsZeroFeeHtlcTx, - CommitmentTypeScriptEnforcedLease: + CommitmentTypeScriptEnforcedLease, + CommitmentTypeSimpleTaproot: return true default: return false } } +// IsTaproot... +func (c CommitmentType) IsTaproot() bool { + return c == CommitmentTypeSimpleTaproot +} + // String returns the name of the CommitmentType. func (c CommitmentType) String() string { switch c { @@ -79,6 +91,8 @@ func (c CommitmentType) String() string { return "anchors-zero-fee-second-level" case CommitmentTypeScriptEnforcedLease: return "script-enforced-lease" + case CommitmentTypeSimpleTaproot: + return "simple-taproot" default: return "invalid" } @@ -137,25 +151,25 @@ func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig { // The reservation workflow consists of the following three steps: // 1. lnwallet.InitChannelReservation // * One requests the wallet to allocate the necessary resources for a -// channel reservation. These resources are put in limbo for the lifetime -// of a reservation. +// channel reservation. These resources are put in limbo for the lifetime +// of a reservation. // * Once completed the reservation will have the wallet's contribution -// accessible via the .OurContribution() method. This contribution -// contains the necessary items to allow the remote party to build both -// the funding, and commitment transactions. +// accessible via the .OurContribution() method. This contribution +// contains the necessary items to allow the remote party to build both +// the funding, and commitment transactions. // 2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution // * The counterparty presents their contribution to the payment channel. -// This allows us to build the funding, and commitment transactions -// ourselves. +// This allows us to build the funding, and commitment transactions +// ourselves. // * We're now able to sign our inputs to the funding transactions, and -// the counterparty's version of the commitment transaction. +// the counterparty's version of the commitment transaction. // * All signatures crafted by us, are now available via .OurSignatures(). // 3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle // * The final step in the workflow. The counterparty presents the -// signatures for all their inputs to the funding transaction, as well -// as a signature to our version of the commitment transaction. +// signatures for all their inputs to the funding transaction, as well +// as a signature to our version of the commitment transaction. // * We then verify the validity of all signatures before considering the -// channel "open". +// channel "open". type ChannelReservation struct { // This mutex MUST be held when either reading or modifying any of the // fields below. @@ -372,6 +386,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.FrozenBit } + if req.CommitType == CommitmentTypeSimpleTaproot { + chanType |= channeldb.SimpleTaprootFeatureBit + } + if req.ZeroConf { chanType |= channeldb.ZeroConfBit } From ae613fbe0c6677ba3863334c9a9d5057481a60f1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Aug 2022 21:35:54 -0700 Subject: [PATCH 23/87] lnwallet: add signing+verification musig2 nonces to ChannelContribution With taproot channels, both sides will send these nonces along side all the other params in open/accept channel. --- lnwallet/reservation.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 65a6255900b..32866bcd603 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -131,6 +132,16 @@ type ChannelContribution struct { // UpfrontShutdown is an optional address to which the channel should be // paid out to on cooperative close. UpfrontShutdown lnwire.DeliveryAddress + + // LocalNonce is populated if the channel type is a simple taproot + // channel. This stores the public (and secret) nonce that will be used + // to generate commitments for the local party. + LocalNonce *musig2.Nonces + + // RemoteNonce is populated if the channel type is a simple taproot + // channel. This stores the public (and secret) nonce that will be used + // to generate commitments for the remote party. + RemoteNonce *musig2.Nonces } // toChanConfig returns the raw channel configuration generated by a node's @@ -214,6 +225,8 @@ type ChannelReservation struct { // nextRevocationKeyLoc stores the key locator information for this // channel. nextRevocationKeyLoc keychain.KeyLocator + + musigSessions *MusigPairSession } // NewChannelReservation creates a new channel reservation. This function is From c68e84d9ec1fb22a26bfd5da5de20096d7d23b5e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:10:45 -0700 Subject: [PATCH 24/87] lnwallet: add MusigPartialSig wrapper over musig2.PartialSignature In this commit, we add a small wrapper struct that implements the input.Signature interface. This'll let us drop this in anywhere the ECDSA signatures are currently accepted. When we serialize the signatures, as we want to pack it in 64 bytes, we use just the x-coordinate of the R point. Note that this means we aren't able to fully reconstruct the R point from just that value (even vs odd). This value is used as input to the challenge hash for verification, and we can recompute R (the full point), given: the combined nonces, the combined key, and the message. --- lnwallet/musig2.go | 365 ------------------------------------- lnwallet/test/btcd/kek.txt | 179 ------------------ musig2_session.go | 81 ++++++++ 3 files changed, 81 insertions(+), 544 deletions(-) delete mode 100644 lnwallet/musig2.go delete mode 100644 lnwallet/test/btcd/kek.txt create mode 100644 musig2_session.go diff --git a/lnwallet/musig2.go b/lnwallet/musig2.go deleted file mode 100644 index 7804f34d3ba..00000000000 --- a/lnwallet/musig2.go +++ /dev/null @@ -1,365 +0,0 @@ -package lnwallet - -import ( - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/keychain" -) - -// MusigPartialSig... -// -// TODO(roasbeef): move to wire package? -type MusigPartialSig struct { - sig *musig2.PartialSignature - - signerNonce [musig2.PubNonceSize]byte - - combinedNonce [musig2.PubNonceSize]byte - - signerKeys []*btcec.PublicKey -} - -// NewMusigPartialSig... -// -// TODO(roasbeef): need version that lets bind the rest later? -func NewMusigPartialSig(sig *musig2.PartialSignature, - signerNonce, combinedNonce [musig2.PubNonceSize]byte, - signerKeys []*btcec.PublicKey) *MusigPartialSig { - - return &MusigPartialSig{ - sig: sig, - signerNonce: signerNonce, - combinedNonce: combinedNonce, - signerKeys: signerKeys, - } -} - -// Serialize serializes the musig2 partial signature. The serializing includes -// the combined nonce _and_ the partial signature. The final signature is -// always 64 bytes in length. -func (p *MusigPartialSig) Serialize() []byte { - var rawSig [schnorr.SignatureSize]byte - - // For the signature, we'll encode only the x-coordinate of the - // combined nonce point. To do this we'll need to convert the R point - // in the sig to jacobian coordinate, and then extract the x-coord from - // that. - // - // TODO(roasbeef): test, or can recompute b, then arrive at the - // combined nonce, given: combinedNonce, combinedKey, msg - var nonceJ btcec.JacobianPoint - p.sig.R.AsJacobian(&nonceJ) - nonceJ.ToAffine() - - nonceX := &nonceJ.X - - nonceX.PutBytesUnchecked(rawSig[:]) - p.sig.S.PutBytesUnchecked(rawSig[32:]) - - return rawSig[:] -} - -// TODO(roasbeef): parse method, can recompute the nonce like above? - -// Verify... -func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { - var m [32]byte - copy(m[:], msg) - - // TODO(roasbeef): need diff nonce here?? - - return p.sig.Verify( - p.signerNonce, p.combinedNonce, p.signerKeys, pub, m, - musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), - ) -} - -// MusigNoncePair... -// -// TODO(roasbeef): rename to nonce1 and nonce2? -// - or signing nonce and verification nonce -type MusigNoncePair struct { - // LocalNonce... - LocalNonce *musig2.Nonces - - // RemoteNonce... - RemoteNonce *musig2.Nonces -} - -// MusigSession... -type MusigSession struct { - session *input.MuSig2SessionInfo - - combinedNonce [musig2.PubNonceSize]byte - - nonces MusigNoncePair - - nextNonces *MusigNoncePair - - // inputTxOut... - inputTxOut *wire.TxOut - - // signerKeys... - signerKeys []*btcec.PublicKey - - // remoteKey... - remoteKey *btcec.PublicKey - - // signer... - signer input.MuSig2Signer - - remoteCommit bool -} - -// NewMusigSession... -func NewMusigSession(noncePair MusigNoncePair, - localKey, remoteKey keychain.KeyDescriptor, - signer input.MuSig2Signer, inputTxOut *wire.TxOut, - remoteCommit bool) (*MusigSession, error) { - - var localNonce, remoteNonce *musig2.Nonces - - // If we're making a session for the remote commitment, then the nonce - // we use to sign is actually our _remote_ nonce, and their - // verification nonce is the local nonce. - switch { - case remoteCommit: - localNonce = noncePair.RemoteNonce - remoteNonce = noncePair.LocalNonce - - // Otherwise, we're generating a signature for our local commitment (to - // broadcast), so we'll use our normal local nonce for signing. - default: - localNonce = noncePair.LocalNonce - remoteNonce = noncePair.RemoteNonce - } - - signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} - tweakDesc := input.MuSig2Tweaks{ - TaprootBIP0086Tweak: true, - } - session, err := signer.MuSig2CreateSession( - localKey.KeyLocator, signerKeys, &tweakDesc, - [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, - musig2.WithPreGeneratedNonce(localNonce), - ) - if err != nil { - return nil, err - } - - // We'll need the raw combined nonces later to be able to verify - // partial signatures, and also combine partial signatures, so we'll - // generate it now ourselves. - combinedNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{ - noncePair.LocalNonce.PubNonce, - noncePair.RemoteNonce.PubNonce, - }) - if err != nil { - return nil, err - } - - return &MusigSession{ - nonces: noncePair, - remoteKey: remoteKey.PubKey, - session: session, - combinedNonce: combinedNonce, - inputTxOut: inputTxOut, - signerKeys: signerKeys, - signer: signer, - remoteCommit: true, - }, nil -} - -// taprootKeyspendSighash... -func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte, - value int64) ([]byte, error) { - - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - pkScript, value, - ) - - sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) - - return txscript.CalcTaprootSignatureHash( - sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, - ) -} - -// SignCommit signs the passed commitment w/ the current signing (relative -// remote) nonce. Given nonces should only ever be used once, once the method -// returns a new nonce is returned, w/ the existing nonce blanked out. -func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.PubNonceSize]byte, error) { - // Before we can sign, we'll need to generate the sighash for their - // commitment transaction. - sigHash, err := taprootKeyspendSighash( - tx, m.inputTxOut.PkScript, m.inputTxOut.Value, - ) - if err != nil { - return nil, nil, err - } - - // Now that we have our session created, we'll use it to generate the - // initial partial signature over our sighash. - var sigHashMsg [32]byte - copy(sigHashMsg[:], sigHash) - - sig, err := m.signer.MuSig2Sign( - m.session.SessionID, sigHashMsg, false, - ) - if err != nil { - return nil, nil, err - } - - // Now that we've generated a signature with this nonce, we'll generate - // another nonce for the _next_ commitment. This'll go in the set of - // nonces for the next state, as we still need the remote party's - // verification nonce (their relative local nonce). - nextSigningNonce, err := musig2.GenNonces() - if err != nil { - return nil, nil, fmt.Errorf("unable to gen new nonce: %w", err) - } - - var nextNonces MusigNoncePair - switch { - case m.remoteCommit: - nextNonces.RemoteNonce = nextSigningNonce - default: - nextNonces.LocalNonce = nextSigningNonce - } - - m.nextNonces = &nextNonces - - return NewMusigPartialSig( - sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, - ), &nextSigningNonce.PubNonce, nil -} - -// TODO(roasbeef): re hot signatures, maybe would re-use the state less signing -// thing after all? -// -// * then able to safely generate nonce deterministically when it comes to -// signing? - -// VerifyCommitSig attempts to verify the passed partial signature against the -// passed commitment transaction. A keyspend sighash is assumed to generate the -// signed message. As we never re-use nonces, a new verification nonce (our -// relative local nonce) returned to transmit to the remote party, which allows -// them to generate another signature. -func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, - sig *musig2.PartialSignature) (*[musig2.PubNonceSize]byte, error) { - - // When we verify a commitment signature, we always assume that we're - // verifying a signature on our local commitment. Therefore, we'll use: - // their remote nonce, and also public key. - partialSig := NewMusigPartialSig( - sig, m.nonces.RemoteNonce.PubNonce, m.combinedNonce, - m.signerKeys, - ) - - // With the partial sig loaded with the proper context, we'll now - // generate the sighash that the remote party should have signed. - sigHash, err := taprootKeyspendSighash( - commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value, - ) - if err != nil { - return nil, err - } - - if !partialSig.Verify(sigHash, m.remoteKey) { - return nil, fmt.Errorf("invalid partial commit sig") - } - - // At this point, we know that their signature is valid, so we'll - // generate another verification nonce for them, so they can generate a - // new state transition. - // - // TODO(roasbeef): do this conditionally? - nextVerificationNonce, err := musig2.GenNonces() - if err != nil { - return nil, fmt.Errorf("unable to gen new nonce: %w", err) - } - - m.nextNonces = &MusigNoncePair{ - RemoteNonce: nextVerificationNonce, - } - - return &nextVerificationNonce.PubNonce, nil -} - -// MusigSessionCfg... -type MusigSessionCfg struct { - // LocalKey... - LocalKey keychain.KeyDescriptor - - // RemoteKey... - RemoteKey keychain.KeyDescriptor - - // LocalCommitNonces... - LocalCommitNonces MusigNoncePair - - // RemoteCommitNonces... - RemoteCommitNonces MusigNoncePair - - // Signer... - Signer input.MuSig2Signer - - // InputTxOut... - InputTxOut *wire.TxOut -} - -// MusigPairSession... -// -// TODO(roasbeef): split this up into two sessions? then can just make one -// later to be able to sign the txns -// -// TODO(roasbeef): chan session? -type MusigPairSession struct { - // LocalSession... - LocalSession *MusigSession - - // RemoteSession... - RemoteSession *MusigSession - - // signer... - signer input.MuSig2Signer -} - -// TODO(roasbeef): move sig here? - -// NewMusigPairSession.... -func NewMusigPairSession(cfg *MusigSessionCfg) (*MusigPairSession, error) { - // Given the config passed in, we'll now create our two sessions: one - // for the local commit, and one for the remote commit. - // - // The session for the local commit uses our local nonce and the remote - // party's remote nonce. The session for the remote commit uses our - // remote nonces, and the remote party's local nonce. - localSession, err := NewMusigSession( - cfg.LocalCommitNonces, cfg.LocalKey, cfg.RemoteKey, - cfg.Signer, cfg.InputTxOut, false, - ) - if err != nil { - return nil, err - } - remoteSession, err := NewMusigSession( - cfg.RemoteCommitNonces, cfg.LocalKey, cfg.RemoteKey, - cfg.Signer, cfg.InputTxOut, true, - ) - if err != nil { - return nil, err - } - - return &MusigPairSession{ - LocalSession: localSession, - RemoteSession: remoteSession, - signer: cfg.Signer, - }, nil -} - -// TODO(roasbeef): chan reest has a late nonce binding diff --git a/lnwallet/test/btcd/kek.txt b/lnwallet/test/btcd/kek.txt deleted file mode 100644 index e0688af9202..00000000000 --- a/lnwallet/test/btcd/kek.txt +++ /dev/null @@ -1,179 +0,0 @@ -=== RUN TestLightningWallet -=== PAUSE TestLightningWallet -=== CONT TestLightningWallet -=== RUN TestLightningWallet/btcwallet/btcd:single_funding_workflow_musig2 -CHAN IS TAPROOT -CHAN IS TAPROOT -MAKING TAPROOT CHAN -pk script gen: 5120370c78e64f9cb754bd2cbcd15602bf0e4a056830c209fa7fe47c71692a0bd2d3 -MAKING TAPROOT CHAN -pk script gen: 5120370c78e64f9cb754bd2cbcd15602bf0e4a056830c209fa7fe47c71692a0bd2d3 -sign key: 02d5fc63d5e45822b926257591509d1f754e16f7d0323854d06db9794270b89173 -sign key: 02d5fc63d5e45822b926257591509d1f754e16f7d0323854d06db9794270b89173 -local nonces: (lnwallet.MusigNoncePair) { - LocalNonce: (*musig2.Nonces)(0x1400077c6c0)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 03 3a 4b 6e 1b 82 91 65 7a e0 9a a9 02 8b 26 83 |.:Kn...ez.....&.| - 00000010 86 ad 4c 30 9c 08 0e 87 5d 7b 06 c6 8e df 97 2e |..L0....]{......| - 00000020 8f 02 fd 51 7e 21 b5 27 3b 01 f9 f0 89 1d 24 5c |...Q~!.';.....$\| - 00000030 79 9c ea 03 06 01 a2 3e 1f e0 c3 8d 0d 1b 80 f1 |y......>........| - 00000040 93 fe |..| - }, - SecNonce: ([64]uint8) (len=64 cap=64) { - 00000000 f0 93 ae ff 97 99 81 2e 58 ec 35 fc 4b 24 47 ec |........X.5.K$G.| - 00000010 34 39 70 f9 1e ef 08 22 d6 23 01 ad 23 10 7f 1f |49p....".#..#...| - 00000020 ea 30 59 1d af b4 a8 72 a1 fb 4b d8 c1 b1 90 75 |.0Y....r..K....u| - 00000030 b4 54 65 2d d8 eb 9a 15 da b1 25 8f 99 77 b3 be |.Te-......%..w..| - } - }), - RemoteNonce: (*musig2.Nonces)(0x1400077ca20)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 02 99 c9 ca 38 27 3f d8 33 69 e2 0b b0 31 4b 83 |....8'?.3i...1K.| - 00000010 96 5c 0e 12 32 b7 60 dc ed 97 6c 92 22 3f e9 09 |.\..2.`...l."?..| - 00000020 32 03 7e 45 0e 89 37 38 36 2c f3 06 9f 3c 42 91 |2.~E..786,......N.3..,..| - 00000040 7d 37 |}7| - }, - SecNonce: ([64]uint8) (len=64 cap=64) { - 00000000 6b 03 cf 62 5f 32 23 65 09 8c 9c 69 f7 9e cb 1c |k..b_2#e...i....| - 00000010 ac 94 fc fd bd ad 74 77 9a 78 7c e3 40 d7 da a3 |......tw.x|.@...| - 00000020 7b c7 6c 9c 39 2d 2b 9c df 69 44 24 6f d7 31 b7 |{.l.9-+..iD$o.1.| - 00000030 94 01 71 82 b4 13 26 0a 45 e3 27 23 f3 7f 48 c7 |..q...&.E.'#..H.| - } - }), - RemoteNonce: (*musig2.Nonces)(0x1400077c750)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 02 a4 83 58 33 b7 69 cd 95 a2 e9 64 5e 5c 99 3a |...X3.i....d^\.:| - 00000010 fb ed 17 c7 09 de 8a 83 80 a1 16 33 fe 8e 25 47 |...........3..%G| - 00000020 56 03 51 ea 4f 37 fd 4d 8a 02 50 5d d6 1e 93 a0 |V.Q.O7.M..P]....| - 00000030 f0 0e 50 57 04 c3 fc 57 51 e4 6a db fd 2f 81 02 |..PW...WQ.j../..| - 00000040 a6 f3 |..| - }, - SecNonce: ([64]uint8) (len=64 cap=64) { - 00000000 6d a9 23 d0 97 aa ed 31 24 a1 53 47 91 3e 4a 22 |m.#....1$.SG.>J"| - 00000010 6c 67 0a c9 9f c2 27 5a 2c 79 72 b3 00 64 4b bf |lg....'Z,yr..dK.| - 00000020 a0 b0 92 06 27 72 c6 0e 0f 67 d1 65 61 c3 4c ae |....'r...g.ea.L.| - 00000030 a5 dc 27 19 b8 b1 52 b9 7a a7 1a a6 84 11 76 1a |..'...R.z.....v.| - } - }) -} - -sighash sign: 79eab9db688aae9241cc7018565cde5ff2d85fa4505f917dbe11d0596f634bea -sign nonce: ([66]uint8) (len=66 cap=66) { - 00000000 02 ba 81 c2 c8 db bf 80 e2 d6 68 8e c1 c4 24 38 |..........h...$8| - 00000010 33 ce 1f 6f ed 75 8c 45 52 6f a6 aa a0 a6 5d be |3..o.u.ERo....].| - 00000020 d8 02 3f 20 11 4d b1 a2 bc ee 69 f7 c1 9f 2e 48 |..? .M....i....H| - 00000030 18 49 e5 64 3e ee d9 c9 4e 84 33 ff ba 2c a9 d9 |.I.d>...N.3..,..| - 00000040 7d 37 |}7| -} - -pk script gen: 5120370c78e64f9cb754bd2cbcd15602bf0e4a056830c209fa7fe47c71692a0bd2d3 -sign key: 02e78d59cebc49c8480a408a7dce39da94d859fbc0291cba640a7de227d80b549a -sign key: 02e78d59cebc49c8480a408a7dce39da94d859fbc0291cba640a7de227d80b549a -local nonces: (lnwallet.MusigNoncePair) { - LocalNonce: (*musig2.Nonces)(0x1400077c990)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 02 ba 81 c2 c8 db bf 80 e2 d6 68 8e c1 c4 24 38 |..........h...$8| - 00000010 33 ce 1f 6f ed 75 8c 45 52 6f a6 aa a0 a6 5d be |3..o.u.ERo....].| - 00000020 d8 02 3f 20 11 4d b1 a2 bc ee 69 f7 c1 9f 2e 48 |..? .M....i....H| - 00000030 18 49 e5 64 3e ee d9 c9 4e 84 33 ff ba 2c a9 d9 |.I.d>...N.3..,..| - 00000040 7d 37 |}7| - }, - SecNonce: ([64]uint8) (len=64 cap=64) { - 00000000 6b 03 cf 62 5f 32 23 65 09 8c 9c 69 f7 9e cb 1c |k..b_2#e...i....| - 00000010 ac 94 fc fd bd ad 74 77 9a 78 7c e3 40 d7 da a3 |......tw.x|.@...| - 00000020 7b c7 6c 9c 39 2d 2b 9c df 69 44 24 6f d7 31 b7 |{.l.9-+..iD$o.1.| - 00000030 94 01 71 82 b4 13 26 0a 45 e3 27 23 f3 7f 48 c7 |..q...&.E.'#..H.| - } - }), - RemoteNonce: (*musig2.Nonces)(0x1400077c750)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 02 a4 83 58 33 b7 69 cd 95 a2 e9 64 5e 5c 99 3a |...X3.i....d^\.:| - 00000010 fb ed 17 c7 09 de 8a 83 80 a1 16 33 fe 8e 25 47 |...........3..%G| - 00000020 56 03 51 ea 4f 37 fd 4d 8a 02 50 5d d6 1e 93 a0 |V.Q.O7.M..P]....| - 00000030 f0 0e 50 57 04 c3 fc 57 51 e4 6a db fd 2f 81 02 |..PW...WQ.j../..| - 00000040 a6 f3 |..| - }, - SecNonce: ([64]uint8) (len=64 cap=64) { - 00000000 6d a9 23 d0 97 aa ed 31 24 a1 53 47 91 3e 4a 22 |m.#....1$.SG.>J"| - 00000010 6c 67 0a c9 9f c2 27 5a 2c 79 72 b3 00 64 4b bf |lg....'Z,yr..dK.| - 00000020 a0 b0 92 06 27 72 c6 0e 0f 67 d1 65 61 c3 4c ae |....'r...g.ea.L.| - 00000030 a5 dc 27 19 b8 b1 52 b9 7a a7 1a a6 84 11 76 1a |..'...R.z.....v.| - } - }) -} - -remote nonces: (lnwallet.MusigNoncePair) { - LocalNonce: (*musig2.Nonces)(0x1400077c6c0)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 03 3a 4b 6e 1b 82 91 65 7a e0 9a a9 02 8b 26 83 |.:Kn...ez.....&.| - 00000010 86 ad 4c 30 9c 08 0e 87 5d 7b 06 c6 8e df 97 2e |..L0....]{......| - 00000020 8f 02 fd 51 7e 21 b5 27 3b 01 f9 f0 89 1d 24 5c |...Q~!.';.....$\| - 00000030 79 9c ea 03 06 01 a2 3e 1f e0 c3 8d 0d 1b 80 f1 |y......>........| - 00000040 93 fe |..| - }, - SecNonce: ([64]uint8) (len=64 cap=64) { - 00000000 f0 93 ae ff 97 99 81 2e 58 ec 35 fc 4b 24 47 ec |........X.5.K$G.| - 00000010 34 39 70 f9 1e ef 08 22 d6 23 01 ad 23 10 7f 1f |49p....".#..#...| - 00000020 ea 30 59 1d af b4 a8 72 a1 fb 4b d8 c1 b1 90 75 |.0Y....r..K....u| - 00000030 b4 54 65 2d d8 eb 9a 15 da b1 25 8f 99 77 b3 be |.Te-......%..w..| - } - }), - RemoteNonce: (*musig2.Nonces)(0x1400077ca20)({ - PubNonce: ([66]uint8) (len=66 cap=66) { - 00000000 02 99 c9 ca 38 27 3f d8 33 69 e2 0b b0 31 4b 83 |....8'?.3i...1K.| - 00000010 96 5c 0e 12 32 b7 60 dc ed 97 6c 92 22 3f e9 09 |.\..2.`...l."?..| - 00000020 32 03 7e 45 0e 89 37 38 36 2c f3 06 9f 3c 42 91 |2.~E..786,... Date: Wed, 31 Aug 2022 16:12:12 -0700 Subject: [PATCH 25/87] lnwallet: add MusigSession struct to encapsulate taproot commit update logic In this commit, we add a new sturct: MusigSession that will handle all the new nonce + partial sig interaction for taproot channels. We also add a basic form of initial signing+verification nonce rotation: when we sign we generate a new signing nonce, when we verif we generate a new verification nonce. --- musig2_session.go | 212 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/musig2_session.go b/musig2_session.go index 6021cc67512..91b885a54d3 100644 --- a/musig2_session.go +++ b/musig2_session.go @@ -79,3 +79,215 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), ) } + +// MusigNoncePair... +// +// TODO(roasbeef): rename to nonce1 and nonce2? +// - or signing nonce and verification nonce +type MusigNoncePair struct { + // LocalNonce... + LocalNonce *musig2.Nonces + + // RemoteNonce... + RemoteNonce *musig2.Nonces +} + +// MusigSession... +type MusigSession struct { + session *input.MuSig2SessionInfo + + combinedNonce [musig2.PubNonceSize]byte + + nonces MusigNoncePair + + nextNonces *MusigNoncePair + + // inputTxOut... + inputTxOut *wire.TxOut + + // signerKeys... + signerKeys []*btcec.PublicKey + + // remoteKey... + remoteKey *btcec.PublicKey + + // signer... + signer input.MuSig2Signer + + remoteCommit bool +} + +// NewMusigSession... +func NewMusigSession(noncePair MusigNoncePair, + localKey, remoteKey keychain.KeyDescriptor, + signer input.MuSig2Signer, inputTxOut *wire.TxOut, + remoteCommit bool) (*MusigSession, error) { + + var localNonce, remoteNonce *musig2.Nonces + + // If we're making a session for the remote commitment, then the nonce + // we use to sign is actually our _remote_ nonce, and their + // verification nonce is the local nonce. + switch { + case remoteCommit: + localNonce = noncePair.RemoteNonce + remoteNonce = noncePair.LocalNonce + + // Otherwise, we're generating a signature for our local commitment (to + // broadcast), so we'll use our normal local nonce for signing. + default: + localNonce = noncePair.LocalNonce + remoteNonce = noncePair.RemoteNonce + } + + signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} + tweakDesc := input.MuSig2Tweaks{ + TaprootBIP0086Tweak: true, + } + session, err := signer.MuSig2CreateSession( + localKey.KeyLocator, signerKeys, &tweakDesc, + [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, + musig2.WithPreGeneratedNonce(localNonce), + ) + if err != nil { + return nil, err + } + + // We'll need the raw combined nonces later to be able to verify + // partial signatures, and also combine partial signatures, so we'll + // generate it now ourselves. + combinedNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{ + noncePair.LocalNonce.PubNonce, + noncePair.RemoteNonce.PubNonce, + }) + if err != nil { + return nil, err + } + + return &MusigSession{ + nonces: noncePair, + remoteKey: remoteKey.PubKey, + session: session, + combinedNonce: combinedNonce, + inputTxOut: inputTxOut, + signerKeys: signerKeys, + signer: signer, + remoteCommit: true, + }, nil +} + +// taprootKeyspendSighash... +func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte, + value int64) ([]byte, error) { + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + pkScript, value, + ) + + sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + return txscript.CalcTaprootSignatureHash( + sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, + ) +} + +// SignCommit signs the passed commitment w/ the current signing (relative +// remote) nonce. Given nonces should only ever be used once, once the method +// returns a new nonce is returned, w/ the existing nonce blanked out. +func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.PubNonceSize]byte, error) { + // Before we can sign, we'll need to generate the sighash for their + // commitment transaction. + sigHash, err := taprootKeyspendSighash( + tx, m.inputTxOut.PkScript, m.inputTxOut.Value, + ) + if err != nil { + return nil, nil, err + } + + // Now that we have our session created, we'll use it to generate the + // initial partial signature over our sighash. + var sigHashMsg [32]byte + copy(sigHashMsg[:], sigHash) + + sig, err := m.signer.MuSig2Sign( + m.session.SessionID, sigHashMsg, false, + ) + if err != nil { + return nil, nil, err + } + + // Now that we've generated a signature with this nonce, we'll generate + // another nonce for the _next_ commitment. This'll go in the set of + // nonces for the next state, as we still need the remote party's + // verification nonce (their relative local nonce). + nextSigningNonce, err := musig2.GenNonces() + if err != nil { + return nil, nil, fmt.Errorf("unable to gen new nonce: %w", err) + } + + var nextNonces MusigNoncePair + switch { + case m.remoteCommit: + nextNonces.RemoteNonce = nextSigningNonce + default: + nextNonces.LocalNonce = nextSigningNonce + } + + m.nextNonces = &nextNonces + + return NewMusigPartialSig( + sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, + ), &nextSigningNonce.PubNonce, nil +} + +// TODO(roasbeef): re hot signatures, maybe would re-use the state less signing +// thing after all? +// +// * then able to safely generate nonce deterministically when it comes to +// signing? + +// VerifyCommitSig attempts to verify the passed partial signature against the +// passed commitment transaction. A keyspend sighash is assumed to generate the +// signed message. As we never re-use nonces, a new verification nonce (our +// relative local nonce) returned to transmit to the remote party, which allows +// them to generate another signature. +func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, + sig *musig2.PartialSignature) (*[musig2.PubNonceSize]byte, error) { + + // When we verify a commitment signature, we always assume that we're + // verifying a signature on our local commitment. Therefore, we'll use: + // their remote nonce, and also public key. + partialSig := NewMusigPartialSig( + sig, m.nonces.RemoteNonce.PubNonce, m.combinedNonce, + m.signerKeys, + ) + + // With the partial sig loaded with the proper context, we'll now + // generate the sighash that the remote party should have signed. + sigHash, err := taprootKeyspendSighash( + commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value, + ) + if err != nil { + return nil, err + } + + if !partialSig.Verify(sigHash, m.remoteKey) { + return nil, fmt.Errorf("invalid partial commit sig") + } + + // At this point, we know that their signature is valid, so we'll + // generate another verification nonce for them, so they can generate a + // new state transition. + // + // TODO(roasbeef): do this conditionally? + nextVerificationNonce, err := musig2.GenNonces() + if err != nil { + return nil, fmt.Errorf("unable to gen new nonce: %w", err) + } + + m.nextNonces = &MusigNoncePair{ + RemoteNonce: nextVerificationNonce, + } + + return &nextVerificationNonce.PubNonce, nil +} From 05a846904b269c7c236b2590bd67044cd8d7002b Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:13:21 -0700 Subject: [PATCH 26/87] lnwallet: add MusigPairSession to handle asymmetric commitment states In this commit, we build upon the prior commit by adding a new MusigPairSession struct. This holds two sessions: a local one and a remote one. A local session is created with the verification nonce of the local party and the signing nonce of the remote party. A remote session is created with the signing nonce of the local party, and the verification nonce of the remote party. --- musig2_session.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/musig2_session.go b/musig2_session.go index 91b885a54d3..7804f34d3ba 100644 --- a/musig2_session.go +++ b/musig2_session.go @@ -291,3 +291,75 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, return &nextVerificationNonce.PubNonce, nil } + +// MusigSessionCfg... +type MusigSessionCfg struct { + // LocalKey... + LocalKey keychain.KeyDescriptor + + // RemoteKey... + RemoteKey keychain.KeyDescriptor + + // LocalCommitNonces... + LocalCommitNonces MusigNoncePair + + // RemoteCommitNonces... + RemoteCommitNonces MusigNoncePair + + // Signer... + Signer input.MuSig2Signer + + // InputTxOut... + InputTxOut *wire.TxOut +} + +// MusigPairSession... +// +// TODO(roasbeef): split this up into two sessions? then can just make one +// later to be able to sign the txns +// +// TODO(roasbeef): chan session? +type MusigPairSession struct { + // LocalSession... + LocalSession *MusigSession + + // RemoteSession... + RemoteSession *MusigSession + + // signer... + signer input.MuSig2Signer +} + +// TODO(roasbeef): move sig here? + +// NewMusigPairSession.... +func NewMusigPairSession(cfg *MusigSessionCfg) (*MusigPairSession, error) { + // Given the config passed in, we'll now create our two sessions: one + // for the local commit, and one for the remote commit. + // + // The session for the local commit uses our local nonce and the remote + // party's remote nonce. The session for the remote commit uses our + // remote nonces, and the remote party's local nonce. + localSession, err := NewMusigSession( + cfg.LocalCommitNonces, cfg.LocalKey, cfg.RemoteKey, + cfg.Signer, cfg.InputTxOut, false, + ) + if err != nil { + return nil, err + } + remoteSession, err := NewMusigSession( + cfg.RemoteCommitNonces, cfg.LocalKey, cfg.RemoteKey, + cfg.Signer, cfg.InputTxOut, true, + ) + if err != nil { + return nil, err + } + + return &MusigPairSession{ + LocalSession: localSession, + RemoteSession: remoteSession, + signer: cfg.Signer, + }, nil +} + +// TODO(roasbeef): chan reest has a late nonce binding From b9a8400a6d312f693c1b99cf8b5b5de79fe58a44 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:14:05 -0700 Subject: [PATCH 27/87] lnwallet/chanfunding: update the funding assemblers to make musig2 funding outputs --- lnwallet/chanfunding/assembler.go | 5 ++++ lnwallet/chanfunding/canned_assembler.go | 29 +++++++++++++++++++++++- lnwallet/chanfunding/psbt_assembler.go | 1 + lnwallet/chanfunding/wallet_assembler.go | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/assembler.go index 4e6e62d259f..7611dba9868 100644 --- a/lnwallet/chanfunding/assembler.go +++ b/lnwallet/chanfunding/assembler.go @@ -78,6 +78,11 @@ type Request struct { // ChangeAddr is a closure that will provide the Assembler with a // change address for the funding transaction if needed. ChangeAddr func() (btcutil.Address, error) + + // Musig2 is true, then musig2 will be used to generate teh funding + // output. By definition, this'll also use segwit v1 (taproot) for the + // funding output. + Musig2 bool } // Intent is returned by an Assembler and represents the base functionality the diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go index 603d90fe273..cb9472f5172 100644 --- a/lnwallet/chanfunding/canned_assembler.go +++ b/lnwallet/chanfunding/canned_assembler.go @@ -35,6 +35,11 @@ type ShimIntent struct { // a normal channel. Until this height, it's considered frozen, so it // can only be cooperatively closed by the responding party. thawHeight uint32 + + // musig2 determines if the funding output should use musig2 to + // generate an aggregate key to use as the taproot-native multi-sig + // output. + musig2 bool } // FundingOutput returns the witness script, and the output that creates the @@ -48,6 +53,19 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) { } totalAmt := s.localFundingAmt + s.remoteFundingAmt + + // If musig2 is active, then we'll return a single aggregated key + // rather than using the "existing" funding script. + if s.musig2 { + // Similar to the existing p2wsh script, we'll always ensure + // the keys are sorted before use. + return input.GenTaprootFundingScript( + s.localKey.PubKey, + s.remoteKey, + int64(totalAmt), + ) + } + return input.GenFundingPkScript( s.localKey.PubKey.SerializeCompressed(), s.remoteKey.SerializeCompressed(), @@ -171,13 +189,20 @@ type CannedAssembler struct { // a normal channel. Until this height, it's considered frozen, so it // can only be cooperatively closed by the responding party. thawHeight uint32 + + // musig2 determines if the funding output should use musig2 to + // generate an aggregate key to use as the taproot-native multi-sig + // output. + musig2 bool } // NewCannedAssembler creates a new CannedAssembler from the material required // to construct a funding output and channel point. +// +// TODO(roasbeef): pass in chan type instead? func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint, fundingAmt btcutil.Amount, localKey *keychain.KeyDescriptor, - remoteKey *btcec.PublicKey, initiator bool) *CannedAssembler { + remoteKey *btcec.PublicKey, initiator, musig2 bool) *CannedAssembler { return &CannedAssembler{ initiator: initiator, @@ -186,6 +211,7 @@ func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint, fundingAmt: fundingAmt, chanPoint: chanPoint, thawHeight: thawHeight, + musig2: musig2, } } @@ -207,6 +233,7 @@ func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) { remoteKey: c.remoteKey, chanPoint: &c.chanPoint, thawHeight: c.thawHeight, + musig2: c.musig2, } if c.initiator { diff --git a/lnwallet/chanfunding/psbt_assembler.go b/lnwallet/chanfunding/psbt_assembler.go index 8632b175d91..5f0bb88e7b8 100644 --- a/lnwallet/chanfunding/psbt_assembler.go +++ b/lnwallet/chanfunding/psbt_assembler.go @@ -523,6 +523,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) { intent := &PsbtIntent{ ShimIntent: ShimIntent{ localFundingAmt: p.fundingAmt, + musig2: req.Musig2, }, State: PsbtShimRegistered, BasePsbt: p.basePsbt, diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index 6d9c1974e0c..b3946aecccd 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -354,6 +354,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) { ShimIntent: ShimIntent{ localFundingAmt: localContributionAmt, remoteFundingAmt: r.RemoteAmt, + musig2: r.Musig2, }, InputCoins: selectedCoins, coinLocker: w.cfg.CoinLocker, From e831860a1ea5a211ea007715d314c07781fdba08 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:17:47 -0700 Subject: [PATCH 28/87] lnwallet: add the pre-tweak CombinedFundingKey to CommitmentKeyRing In this commit, we add another item to the commitment key ring: the CommitmentKeyRing. This'll be used for situations where we want to force a script path only. For example, the to_remote output on the party's commitment transaction. We use the pre-tweaked key here so we can avoid having to deal w/ two levels of tweaks. --- lnwallet/commitment.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index c4f4d931a40..49622af7211 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -95,6 +95,13 @@ type CommitmentKeyRing struct { // If this is our commitment, it means the remote node can sign for // this key in case of a breach. RevocationKey *btcec.PublicKey + + // CombinedFundingKey is the taproot+musig2 funding key. This is the + // key _before_ the BIP 86 tweak is applied. + // + // NOTE: This will only be set if this is the KeyRing for a taproot + // channel. + CombinedFundingKey *btcec.PublicKey } // DeriveCommitmentKeys generates a new commitment key set using the base points @@ -177,6 +184,21 @@ func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, ) } + // If this is a taproot commitment, then we'll use the funding keys to + // generate a combined funding key. + if chanType.IsTaproot() { + musigKey, _, _, _ := musig2.AggregateKeys( + []*btcec.PublicKey{ + localChanCfg.MultiSigKey.PubKey, + remoteChanCfg.MultiSigKey.PubKey, + }, + true, + musig2.WithBIP86KeyTweak(), + ) + + keyRing.CombinedFundingKey = musigKey.PreTweakedKey + } + return keyRing } From 92b3a43fe0946438ddbcd7c0186db72490acc933 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:18:18 -0700 Subject: [PATCH 29/87] lnwallet: update CommitScriptToSelf to conditionally make P2TR outputs --- lnwallet/commitment.go | 77 +++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 49622af7211..45ef50a1bd3 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -224,36 +224,75 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) ( *ScriptInfo, error) { - var ( - toLocalRedeemScript []byte - err error - ) switch { + // For taproot scripts, we'll need to make a slightly modified script + // where the top level key is the revocation case, with our CSV timeout + // path living in a tapscript leaf. + // + // Our "redeem" script here is just the taproot witness program. + // + // TODO(roasbeef): rework ScriptInfo struct to have taproot specific + // info? + case chanType.IsTaproot(): + toLocalOutputKey, err := input.TaprootCommitScriptToSelf( + csvDelay, selfKey, revokeKey, + ) + if err != nil { + return nil, fmt.Errorf("unable to generate taproot "+ + "key: %w", err) + } + + toLocalPkScript, err := input.PayToTaprootScript( + toLocalOutputKey, + ) + + return &ScriptInfo{ + PkScript: toLocalPkScript, + }, nil + // If we are the initiator of a leased channel, then we have an - // additional CLTV requirement in addition to the usual CSV requirement. + // additional CLTV requirement in addition to the usual CSV + // requirement. case initiator && chanType.HasLeaseExpiration(): - toLocalRedeemScript, err = input.LeaseCommitScriptToSelf( + toLocalRedeemScript, err := input.LeaseCommitScriptToSelf( selfKey, revokeKey, csvDelay, leaseExpiry, ) + if err != nil { + return nil, err + } + + toLocalScriptHash, err := input.WitnessScriptHash( + toLocalRedeemScript, + ) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: toLocalScriptHash, + WitnessScript: toLocalRedeemScript, + }, nil default: - toLocalRedeemScript, err = input.CommitScriptToSelf( + toLocalRedeemScript, err := input.CommitScriptToSelf( csvDelay, selfKey, revokeKey, ) - } - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - toLocalScriptHash, err := input.WitnessScriptHash(toLocalRedeemScript) - if err != nil { - return nil, err - } + toLocalScriptHash, err := input.WitnessScriptHash( + toLocalRedeemScript, + ) + if err != nil { + return nil, err + } - return &ScriptInfo{ - PkScript: toLocalScriptHash, - WitnessScript: toLocalRedeemScript, - }, nil + return &ScriptInfo{ + PkScript: toLocalScriptHash, + WitnessScript: toLocalRedeemScript, + }, nil + } } // CommitScriptToRemote derives the appropriate to_remote script based on the From d51facd12f09303bec825d650a84aed53007ce02 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:18:48 -0700 Subject: [PATCH 30/87] lnwallet: update CommitScriptToRemote to conditionally make P2TR outputs --- lnwallet/commitment.go | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 45ef50a1bd3..ed2dfbc17e6 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -212,6 +213,9 @@ type ScriptInfo struct { // output is being signed. For p2wkh it should be set equal to the // PkScript. WitnessScript []byte + + // TODO(roasbeef): embed the waddr taproot info? + // * can list the leaves, etc, etc } // CommitScriptToSelf constructs the public key script for the output on the @@ -300,8 +304,11 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // owner of the commitment transaction which we are generating the to_remote // script for. The second return value is the CSV delay of the output script, // what must be satisfied in order to spend the output. +// +// NOTE: The combinedFundingKey MUST be set if chanType is a taproot variant. func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, - key *btcec.PublicKey, leaseExpiry uint32) (*ScriptInfo, uint32, error) { + remoteKey *btcec.PublicKey, leaseExpiry uint32, + combinedFundingKey *btcec.PublicKey) (*ScriptInfo, uint32, error) { switch { // If we are not the initiator of a leased channel, then the remote @@ -309,7 +316,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, // CSV requirement. case chanType.HasLeaseExpiration() && !initiator: script, err := input.LeaseCommitScriptToRemoteConfirmed( - key, leaseExpiry, + remoteKey, leaseExpiry, ) if err != nil { return nil, 0, err @@ -328,7 +335,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, // If this channel type has anchors, we derive the delayed to_remote // script. case chanType.HasAnchors(): - script, err := input.CommitScriptToRemoteConfirmed(key) + script, err := input.CommitScriptToRemoteConfirmed(remoteKey) if err != nil { return nil, 0, err } @@ -343,9 +350,29 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, WitnessScript: script, }, 1, nil + // For taproot channels, we'll use a slightly different format, where + // the top-level key is the combined funding key (w/o the bip 86 + // tweak), with the sole tap leaf enforcing the 1 CSV delay. + case chanType.IsTaproot(): + toRemoteKey, err := input.TaprootCommitScriptToRemote( + combinedFundingKey, remoteKey, + ) + if err != nil { + return nil, 0, err + } + + toRemotePkScript, err := input.PayToTaprootScript(toRemoteKey) + if err != nil { + return nil, 0, err + } + + return &ScriptInfo{ + PkScript: toRemotePkScript, + }, 1, nil + default: // Otherwise the to_remote will be a simple p2wkh. - p2wkh, err := input.CommitScriptUnencumbered(key) + p2wkh, err := input.CommitScriptUnencumbered(remoteKey) if err != nil { return nil, 0, err } From d6eeda25c622b1083db1e65f298be41d7180e9e3 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:19:15 -0700 Subject: [PATCH 31/87] lnwallet: update CommitScriptAnchors to conditionally make P2TR outputs --- lnwallet/commitment.go | 87 +++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index ed2dfbc17e6..ddc1d6e3163 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -514,37 +514,87 @@ func HtlcSuccessFee(chanType channeldb.ChannelType, // CommitScriptAnchors return the scripts to use for the local and remote // anchor. -func CommitScriptAnchors(localChanCfg, - remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, +func CommitScriptAnchors(chanType channeldb.ChannelType, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, *ScriptInfo, error) { - // Helper to create anchor ScriptInfo from key. - anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) { - script, err := input.CommitScriptAnchor(key) - if err != nil { - return nil, err + var ( + anchorScript func(key *btcec.PublicKey) (*ScriptInfo, error) + keySelector func(*channeldb.ChannelConfig, bool) *btcec.PublicKey + ) + + switch { + // For taproot channels, the anchor is slightly different: the top + // level key is now the (relative) local delay and remote public key, + // since these are fully revealed once the commitment hits the chain. + // + // TODO(roasbeef): need to re-examine the assumption of what's + // revealed? otherwise then have two levels of tweaks... + case chanType.IsTaproot(): + anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + anchorKey, err := input.TaprootOutputKeyAnchor(key) + if err != nil { + return nil, err + } + + anchorPkScript, err := input.PayToTaprootScript(anchorKey) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: anchorPkScript, + }, nil } - scriptHash, err := input.WitnessScriptHash(script) - if err != nil { - return nil, err + keySelector = func(cfg *channeldb.ChannelConfig, + local bool) *btcec.PublicKey { + + if local { + return cfg.DelayBasePoint.PubKey + } + + return cfg.PaymentBasePoint.PubKey } - return &ScriptInfo{ - PkScript: scriptHash, - WitnessScript: script, - }, nil + // For normal channels we'll use the multi-sig keys since those are + // revealed when the channel closes + default: + // For normal channels, we'll create a p2wsh script based on + // the target key. + anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + script, err := input.CommitScriptAnchor(key) + if err != nil { + return nil, err + } + + scriptHash, err := input.WitnessScriptHash(script) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: scriptHash, + WitnessScript: script, + }, nil + } + + // For the existing channels, we'll always select the multi-sig + // key from the party's channel config. + keySelector = func(cfg *channeldb.ChannelConfig, _ bool) *btcec.PublicKey { + return cfg.MultiSigKey.PubKey + } } // Get the script used for the anchor output spendable by the local // node. - localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey) + localAnchor, err := anchorScript(keySelector(localChanCfg, true)) if err != nil { return nil, nil, err } // And the anchor spendable by the remote node. - remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey) + remoteAnchor, err := anchorScript(keySelector(remoteChanCfg, false)) if err != nil { return nil, nil, err } @@ -841,6 +891,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // Next, we create the script paying to the remote. toRemoteScript, _, err := CommitScriptToRemote( chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, + keyRing.CombinedFundingKey, ) if err != nil { return nil, err @@ -872,7 +923,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // If this channel type has anchors, we'll also add those. if chanType.HasAnchors() { localAnchor, remoteAnchor, err := CommitScriptAnchors( - localChanCfg, remoteChanCfg, + chanType, localChanCfg, remoteChanCfg, ) if err != nil { return nil, err @@ -1105,7 +1156,7 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, // commitment, the to remote output belongs to us. ourScript, _, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, keyRing.CombinedFundingKey, ) if err != nil { return ourIndex, theirIndex, err From a8554f436af1f31a0b596294456f7a051a0c8d19 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:19:52 -0700 Subject: [PATCH 32/87] lnwallet: create signing+verification nonces for taproot channel funding --- lnwallet/wallet.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 49c6410beb3..0d6d6c1191a 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -1206,6 +1206,20 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation, reservation.partialState.RevocationProducer = producer reservation.ourContribution.ChannelConstraints = l.Cfg.DefaultConstraints + // If taproot channels are active, then we'll generate two sets of + // nonces: one for our local commitment, and one for their remote + // commitment. + if reservation.partialState.ChanType.IsTaproot() { + reservation.ourContribution.LocalNonce, err = musig2.GenNonces() + if err != nil { + return err + } + reservation.ourContribution.RemoteNonce, err = musig2.GenNonces() + if err != nil { + return err + } + } + return nil } From 1b121a363e78cfbf2caaca36f6ec3e1458e1cc7f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:20:22 -0700 Subject: [PATCH 33/87] funding: send signing+verification nonces in open/accept channel for taproot chans --- funding/manager.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/funding/manager.go b/funding/manager.go index d10872bf62f..fe974c18227 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -1659,6 +1659,21 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, log.Debugf("Remote party accepted commitment constraints: %v", spew.Sdump(remoteContribution.ChannelConfig.ChannelConstraints)) + ourContribution := reservation.OurContribution() + + var ( + localNonce *lnwire.LocalMusig2Nonce + remoteNonce *lnwire.RemoteMusig2Nonce + ) + if commitType == lnwallet.CommitmentTypeSimpleTaproot { + localNonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: ourContribution.LocalNonce.PubNonce, + } + remoteNonce = &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: ourContribution.RemoteNonce.PubNonce, + } + } + // With the initiator's contribution recorded, respond with our // contribution in the next message of the workflow. ourContribution := reservation.OurContribution() @@ -1680,6 +1695,8 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, UpfrontShutdownScript: ourContribution.UpfrontShutdown, ChannelType: chanTypeFeatureBits, LeaseExpiry: msg.LeaseExpiry, + LocalNonce: localNonce, + RemoteNonce: remoteNonce, } if err := peer.SendMessage(true, &fundingAccept); err != nil { @@ -4037,6 +4054,19 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { log.Infof("Starting funding workflow with %v for pending_id(%x), "+ "committype=%v", msg.Peer.Address(), chanID, commitType) + var ( + localNonce *lnwire.LocalMusig2Nonce + remoteNonce *lnwire.RemoteMusig2Nonce + ) + if commitType == lnwallet.CommitmentTypeSimpleTaproot { + localNonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: ourContribution.LocalNonce.PubNonce, + } + remoteNonce = &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: ourContribution.RemoteNonce.PubNonce, + } + } + fundingOpen := lnwire.OpenChannel{ ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash, PendingChannelID: chanID, @@ -4059,6 +4089,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { UpfrontShutdownScript: shutdown, ChannelType: chanType, LeaseExpiry: leaseExpiry, + LocalNonce: localNonce, + RemoteNonce: remoteNonce, } if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil { e := fmt.Errorf("unable to send funding request message: %v", From ac82dd8f387d476cdd06006faf5cacc9b95d8a61 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:20:58 -0700 Subject: [PATCH 34/87] lnwallet: pass thru musig2 context for chan funder requests --- lnwallet/wallet.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 0d6d6c1191a..87a0a73b7f3 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -778,6 +778,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg TaprootPubkey, true, DefaultAccountName, ) }, + Musig2: req.CommitType == CommitmentTypeSimpleTaproot, } fundingIntent, err = req.ChanFunder.ProvisionChannel( fundingReq, From 505c1907476f0f44adf8a82b791bd3abac0ff68c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:22:14 -0700 Subject: [PATCH 35/87] lnwallet: add internal funding support for signing+verifying musig2 commitments --- lnwallet/wallet.go | 319 +++++++++++++++++++++++++++++++++------------ 1 file changed, 234 insertions(+), 85 deletions(-) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 87a0a73b7f3..ab4af9e1d89 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcutil/txsort" @@ -1477,8 +1478,110 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { }) } -// handleChanPointReady continues the funding process once the channel point -// is known and the funding transaction can be completed. +// genMusigSession... +func genMusigSession(ourContribution, theirContribution *ChannelContribution, + signer input.MuSig2Signer, + fundingOutput *wire.TxOut) (*MusigPairSession, error) { + + sessionCfg := &MusigSessionCfg{ + LocalKey: ourContribution.MultiSigKey, + RemoteKey: theirContribution.MultiSigKey, + LocalCommitNonces: MusigNoncePair{ + LocalNonce: ourContribution.LocalNonce, + RemoteNonce: theirContribution.RemoteNonce, + }, + RemoteCommitNonces: MusigNoncePair{ + LocalNonce: theirContribution.LocalNonce, + RemoteNonce: ourContribution.RemoteNonce, + }, + Signer: signer, + InputTxOut: fundingOutput, + } + musigSessions, err := NewMusigPairSession( + sessionCfg, + ) + if err != nil { + return nil, fmt.Errorf("unable to gen musig session: %w", err) + } + + return musigSessions, nil +} + +// signCommitTx... +func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, + commitTx *wire.MsgTx, fundingOutput *wire.TxOut, + fundingWitnessScript []byte) (input.Signature, error) { + + ourContribution := pendingReservation.ourContribution + theirContribution := pendingReservation.theirContribution + + var ( + sigTheirCommit input.Signature + err error + ) + switch { + // If this is a taproot channel, then we'll need to create an initial + // musig2 session here as we'll be sending over a _partial_ signature. + case pendingReservation.partialState.ChanType.IsTaproot(): + // We're now ready to sign the first commitment. However, we'll + // only create the session if that hasn't been done already. + if pendingReservation.musigSessions == nil { + musigSessions, err := genMusigSession( + ourContribution, theirContribution, l.Cfg.Signer, + fundingOutput, + ) + if err != nil { + return nil, err + } + + pendingReservation.musigSessions = musigSessions + } + + // Now that we have the funding outpoint, we'll generate a + // musig2 signature for their version of the commitment + // transaction. We use the remote session as this is for the + // remote commitment transaction. + // + // TODO(roasbeef): keep the signing nonce here? or just always + // regen for funding_locked? + musigSessions := pendingReservation.musigSessions + partialSig, _, err := musigSessions.RemoteSession.SignCommit( + commitTx, + ) + if err != nil { + return nil, fmt.Errorf("unable to sign "+ + "commitment: %w", err) + } + + sigTheirCommit = partialSig + + // For regular channels, we can just send over a normal ECDSA signature + // w/o any extra steps. + default: + ourKey := ourContribution.MultiSigKey + signDesc := input.SignDescriptor{ + WitnessScript: fundingWitnessScript, + KeyDesc: ourKey, + Output: fundingOutput, + HashType: txscript.SigHashAll, + SigHashes: input.NewTxSigHashesV0Only( + commitTx, + ), + InputIndex: 0, + } + sigTheirCommit, err = l.Cfg.Signer.SignOutputRaw( + commitTx, &signDesc, + ) + if err != nil { + return nil, err + } + } + + return sigTheirCommit, nil +} + +// handleChanPointReady continues the funding process once the channel point is +// known and the funding transaction can be completed. func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] @@ -1488,6 +1591,7 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { "funding state") return } + ourContribution := pendingReservation.ourContribution theirContribution := pendingReservation.theirContribution chanPoint := pendingReservation.partialState.FundingOutpoint @@ -1628,26 +1732,21 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { fundingIntent := pendingReservation.fundingIntent fundingWitnessScript, fundingOutput, err := fundingIntent.FundingOutput() if err != nil { - req.err <- fmt.Errorf("unable to obtain funding output") + req.err <- fmt.Errorf("unable to obtain funding output: %w", err) return } // Generate a signature for their version of the initial commitment // transaction. - ourKey := ourContribution.MultiSigKey - signDesc := input.SignDescriptor{ - WitnessScript: fundingWitnessScript, - KeyDesc: ourKey, - Output: fundingOutput, - HashType: txscript.SigHashAll, - SigHashes: input.NewTxSigHashesV0Only(theirCommitTx), - InputIndex: 0, - } - sigTheirCommit, err := l.Cfg.Signer.SignOutputRaw(theirCommitTx, &signDesc) + sigTheirCommit, err := l.signCommitTx( + pendingReservation, theirCommitTx, fundingOutput, + fundingWitnessScript, + ) if err != nil { req.err <- err return } + pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil @@ -1772,6 +1871,89 @@ func (l *LightningWallet) verifyFundingInputs(fundingTx *wire.MsgTx, return nil } +// verifyCommitSig... +func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, + commitSig input.Signature, commitTx *wire.MsgTx) error { + + localKey := res.ourContribution.MultiSigKey.PubKey + remoteKey := res.theirContribution.MultiSigKey.PubKey + channelValue := int64(res.partialState.Capacity) + + // If this isn't a taproot channel, then we'll construct a segwit v0 + // p2wsh sighash. + switch { + case !res.partialState.ChanType.IsTaproot(): + + hashCache := input.NewTxSigHashesV0Only(commitTx) + witnessScript, _, err := input.GenFundingPkScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), channelValue, + ) + if err != nil { + return err + } + + sigHash, err := txscript.CalcWitnessSigHash( + witnessScript, hashCache, txscript.SigHashAll, + commitTx, 0, channelValue, + ) + if err != nil { + return err + } + + // Verify that we've received a valid signature from the remote + // party for our version of the commitment transaction. + if !commitSig.Verify(sigHash, remoteKey) { + return fmt.Errorf("counterparty's commitment " + + "signature is invalid") + } + + return nil + + // Otherwise for taproot channels, we'll compute the segwit v1 sighash, + // which is slightly different. + default: + // First, check to see if we've generated the musig session + // already. If we're the responder in the funding flow, we may + // not have generated it already. + if res.musigSessions == nil { + _, fundingOutput, err := input.GenTaprootFundingScript( + localKey, remoteKey, channelValue, + ) + if err != nil { + return err + } + + res.musigSessions, err = genMusigSession( + res.ourContribution, res.theirContribution, + l.Cfg.Signer, fundingOutput, + ) + if err != nil { + return err + } + } + + // For the musig2 based channels, we'll use the generated local + // musig2 session to verify the signature. + localSession := res.musigSessions.LocalSession + + // At this point, the commitment signature passed in should + // actually be a wrapped musig2 signature, so we'll do a type + // asset to the get the signature we actually need. + switch partialSig := commitSig.(type) { + case *MusigPartialSig: + _, err := localSession.VerifyCommitSig( + commitTx, partialSig.sig, + ) + return err + + default: + return fmt.Errorf("expected *musig2.PartialSignature, "+ + "got: %T", commitSig) + } + } +} + // handleFundingCounterPartySigs is the final step in the channel reservation // workflow. During this step, we validate *all* the received signatures for // inputs to the funding transaction. If any of these are invalid, we bail, @@ -1814,44 +1996,18 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // commitment transaction. res.theirCommitmentSig = msg.theirCommitmentSig commitTx := res.partialState.LocalCommitment.CommitTx - ourKey := res.ourContribution.MultiSigKey - theirKey := res.theirContribution.MultiSigKey - - // Re-generate both the witnessScript and p2sh output. We sign the - // witnessScript script, but include the p2sh output as the subscript - // for verification. - witnessScript, _, err := input.GenFundingPkScript( - ourKey.PubKey.SerializeCompressed(), - theirKey.PubKey.SerializeCompressed(), - int64(res.partialState.Capacity), - ) - if err != nil { - msg.err <- err - msg.completeChan <- nil - return - } - // Next, create the spending scriptSig, and then verify that the script - // is complete, allowing us to spend from the funding transaction. - channelValue := int64(res.partialState.Capacity) - hashCache := input.NewTxSigHashesV0Only(commitTx) - sigHash, err := txscript.CalcWitnessSigHash( - witnessScript, hashCache, txscript.SigHashAll, commitTx, - 0, channelValue, - ) + err := l.verifyCommitSig(res, msg.theirCommitmentSig, commitTx) if err != nil { - msg.err <- err + msg.err <- fmt.Errorf("counterparty's commitment signature is "+ + "invalid: %w", err) msg.completeChan <- nil return } - // Verify that we've received a valid signature from the remote party - // for our version of the commitment transaction. - if !msg.theirCommitmentSig.Verify(sigHash, theirKey.PubKey) { - msg.err <- fmt.Errorf("counterparty's commitment signature is invalid") - msg.completeChan <- nil - return - } + // TODO(roasbeef): need to convert sig into something actual? + // * also actually verify against target session? + theirCommitSigBytes := msg.theirCommitmentSig.Serialize() res.partialState.LocalCommitment.CommitSig = theirCommitSigBytes @@ -1930,6 +2086,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { defer pendingReservation.Unlock() chanState := pendingReservation.partialState + chanType := pendingReservation.partialState.ChanType chanState.FundingOutpoint = *req.fundingOutpoint fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil) @@ -1948,7 +2105,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { pendingReservation.theirContribution.ChannelConfig, pendingReservation.ourContribution.FirstCommitmentPoint, pendingReservation.theirContribution.FirstCommitmentPoint, - *fundingTxIn, pendingReservation.partialState.ChanType, + *fundingTxIn, chanType, pendingReservation.partialState.IsInitiator, leaseExpiry, ) if err != nil { @@ -1984,13 +2141,10 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { walletLog.Debugf("Remote commit tx for ChannelPoint(%v): %v", req.fundingOutpoint, spew.Sdump(theirCommitTx)) - channelValue := int64(pendingReservation.partialState.Capacity) - hashCache := input.NewTxSigHashesV0Only(ourCommitTx) - theirKey := pendingReservation.theirContribution.MultiSigKey - ourKey := pendingReservation.ourContribution.MultiSigKey - witnessScript, _, err := input.GenFundingPkScript( - ourKey.PubKey.SerializeCompressed(), - theirKey.PubKey.SerializeCompressed(), channelValue, + // With both commitment transactions created, we'll now verify their + // signature on our commitment. + err = l.verifyCommitSig( + pendingReservation, req.theirCommitmentSig, ourCommitTx, ) if err != nil { req.err <- err @@ -1998,53 +2152,46 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { return } - sigHash, err := txscript.CalcWitnessSigHash( - witnessScript, hashCache, txscript.SigHashAll, ourCommitTx, 0, - channelValue, + theirCommitSigBytes := req.theirCommitmentSig.Serialize() + chanState.LocalCommitment.CommitSig = theirCommitSigBytes + + channelValue := int64(pendingReservation.partialState.Capacity) + theirKey := pendingReservation.theirContribution.MultiSigKey + ourKey := pendingReservation.ourContribution.MultiSigKey + + var ( + fundingWitnessScript []byte + fundingTxOut *wire.TxOut ) + if chanType.IsTaproot() { + fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( + ourKey.PubKey, theirKey.PubKey, channelValue, + ) + } else { + fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( + ourKey.PubKey.SerializeCompressed(), + theirKey.PubKey.SerializeCompressed(), channelValue, + ) + } if err != nil { req.err <- err req.completeChan <- nil return } - // Verify that we've received a valid signature from the remote party - // for our version of the commitment transaction. - if !req.theirCommitmentSig.Verify(sigHash, theirKey.PubKey) { - req.err <- fmt.Errorf("counterparty's commitment signature " + - "is invalid") - req.completeChan <- nil - return - } - theirCommitSigBytes := req.theirCommitmentSig.Serialize() - chanState.LocalCommitment.CommitSig = theirCommitSigBytes - // With their signature for our version of the commitment transactions // verified, we can now generate a signature for their version, // allowing the funding transaction to be safely broadcast. - p2wsh, err := input.WitnessScriptHash(witnessScript) - if err != nil { - req.err <- err - req.completeChan <- nil - return - } - signDesc := input.SignDescriptor{ - WitnessScript: witnessScript, - KeyDesc: ourKey, - Output: &wire.TxOut{ - PkScript: p2wsh, - Value: channelValue, - }, - HashType: txscript.SigHashAll, - SigHashes: input.NewTxSigHashesV0Only(theirCommitTx), - InputIndex: 0, - } - sigTheirCommit, err := l.Cfg.Signer.SignOutputRaw(theirCommitTx, &signDesc) + sigTheirCommit, err := l.signCommitTx( + pendingReservation, theirCommitTx, fundingTxOut, + fundingWitnessScript, + ) if err != nil { req.err <- err req.completeChan <- nil return } + pendingReservation.ourCommitmentSig = sigTheirCommit _, bestHeight, err := l.Cfg.ChainIO.GetBestBlock() @@ -2168,6 +2315,8 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, return err } + // TODO(roasbeef): update to understand taproot + // Finally, we'll pass in all the necessary context needed to fully // validate that this channel is indeed what we expect, and can be // used. From 1c9efdd87ee95bd71a4d4b770af01c638bba0573 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:22:27 -0700 Subject: [PATCH 36/87] lnwallet: add new test case for musig2 single funder workflows --- lnwallet/test/test_interface.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index ebaf91e8d94..da3e3ed029f 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -2748,6 +2748,19 @@ var walletTests = []walletTestCase{ ) }, }, + { + name: "single funding workflow musig2", + test: func(miner *rpctest.Harness, alice, + bob *lnwallet.LightningWallet, t *testing.T) { + + testSingleFunderReservationWorkflow( + miner, alice, bob, t, + lnwallet.CommitmentTypeSimpleTaproot, nil, + nil, [32]byte{}, 0, + ) + }, + }, + // TODO(roasbeef): add musig2 external funding { name: "single funding workflow external funding tx", test: testSingleFunderExternalFundingTx, @@ -2962,11 +2975,11 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness, thawHeight := uint32(200) aliceExternalFunder := chanfunding.NewCannedAssembler( thawHeight, *chanPoint, btcutil.Amount(chanAmt), &aliceFundingKey, - bobFundingKey.PubKey, true, + bobFundingKey.PubKey, true, false, ) bobShimIntent, err := chanfunding.NewCannedAssembler( thawHeight, *chanPoint, btcutil.Amount(chanAmt), &bobFundingKey, - aliceFundingKey.PubKey, false, + aliceFundingKey.PubKey, false, false, ).ProvisionChannel(&chanfunding.Request{ LocalAmt: btcutil.Amount(chanAmt), MinConfs: 1, From 710e76aae569393bc0bd7337c54571c1db4b94e5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:24:10 -0700 Subject: [PATCH 37/87] lnwallet: add initial awareness for musig2 sessions to channel state machine The two new methods will be used to generate the nonces we send for the chan_reest message, and also funding locked. When we receive these messages from the remote party, these methods will be called in the link, and then permit new states to be created. --- lnwallet/channel.go | 97 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 16c412f3297..0a05309c2f0 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1312,6 +1312,15 @@ type LightningChannel struct { // log is a channel-specific logging instance. log btclog.Logger + // musigSessions... + musigSessions *MusigPairSession + + // nextNoncePair... + nextNoncePair *MusigNoncePair + + // fundingOutput... + fundingOutput wire.TxOut + sync.RWMutex } @@ -1389,15 +1398,16 @@ func (lc *LightningChannel) createSignDesc() error { if err != nil { return err } + lc.fundingOutput = wire.TxOut{ + PkScript: fundingPkScript, + Value: int64(lc.channelState.Capacity), + } lc.signDesc = &input.SignDescriptor{ KeyDesc: lc.channelState.LocalChanCfg.MultiSigKey, WitnessScript: multiSigScript, - Output: &wire.TxOut{ - PkScript: fundingPkScript, - Value: int64(lc.channelState.Capacity), - }, - HashType: txscript.SigHashAll, - InputIndex: 0, + Output: &lc.fundingOutput, + HashType: txscript.SigHashAll, + InputIndex: 0, } return nil @@ -2337,7 +2347,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, isRemoteInitiator := !chanState.IsInitiator ourScript, ourDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, keyRing.CombinedFundingKey, ) if err != nil { return nil, err @@ -5812,7 +5822,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // transaction. selfScript, maturityDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, keyRing.CombinedFundingKey, ) if err != nil { return nil, fmt.Errorf("unable to create self commit "+ @@ -6825,7 +6835,8 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, // Derive our local anchor script. localAnchor, _, err := CommitScriptAnchors( - &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + chanState.ChanType, &chanState.LocalChanCfg, + &chanState.RemoteChanCfg, ) if err != nil { return nil, err @@ -7500,3 +7511,71 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex, return localPeerUpdates } + +// GenMusigNonces generates the signing and verification nonces to start off a +// new musig2 channel session. +func (lc *LightningChannel) GenMusigNonces() (*MusigNoncePair, error) { + lc.RLock() + defer lc.RUnlock() + + verificationNonce, err := musig2.GenNonces() + if err != nil { + return nil, err + } + + signingNonce, err := musig2.GenNonces() + if err != nil { + return nil, err + } + + lc.nextNoncePair = &MusigNoncePair{ + LocalNonce: verificationNonce, + RemoteNonce: signingNonce, + } + + return lc.nextNoncePair, nil +} + +// InitRemoteMusigNonces processes the remote musig nonces sent by the remote +// party. This should be called upon connection re-establishment, after we've +// generated our own nonces. Once this method returns a nil error, then the +// channel can be used to sign commitment states. +func (lc *LightningChannel) InitRemoteMusigNonces(nonces *MusigNoncePair) error { + lc.RLock() + defer lc.RUnlock() + + // Now that we have the set of local and remote nonces, we can generate + // a new pair of musig sessions for our local commitment and the + // commitment of the remote party. + remoteNonces := nonces + localNonces := lc.nextNoncePair + + localChanCfg := lc.channelState.LocalChanCfg + remoteChanCfg := lc.channelState.RemoteChanCfg + + // TODO(roasbeef): propagate rename of signing and verification nonces + + var err error + sessionCfg := &MusigSessionCfg{ + LocalKey: localChanCfg.MultiSigKey, + RemoteKey: remoteChanCfg.MultiSigKey, + LocalCommitNonces: MusigNoncePair{ + LocalNonce: localNonces.LocalNonce, + RemoteNonce: remoteNonces.RemoteNonce, + }, + RemoteCommitNonces: MusigNoncePair{ + LocalNonce: remoteNonces.LocalNonce, + RemoteNonce: localNonces.RemoteNonce, + }, + Signer: lc.Signer, + InputTxOut: &lc.fundingOutput, + } + lc.musigSessions, err = NewMusigPairSession( + sessionCfg, + ) + if err != nil { + return fmt.Errorf("unable to gen musig session: %w", err) + } + + return nil +} From 7fe992907246bb6bb89e00cb97fe41212cfe9f39 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 16:24:52 -0700 Subject: [PATCH 38/87] lnwallet: update RemoteNextRevocation to return nil if no nonces known for taproot chans This ensures that EligibleToForward in the link returns false until we have the nonces we need to propose+accept a commitment state. --- lnwallet/channel.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 0a05309c2f0..bec08361d97 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -7327,11 +7327,23 @@ func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate, return absoluteMaxFee } -// RemoteNextRevocation returns the channelState's RemoteNextRevocation. +// RemoteNextRevocation returns the channelState's RemoteNextRevocation. For +// musig2 channels, until a nonce pair is processed by the remote party, a nil +// public key is returned. +// +// TODO(roasbeef): revisit, maybe just make a more general method instead? func (lc *LightningChannel) RemoteNextRevocation() *btcec.PublicKey { lc.RLock() defer lc.RUnlock() + if !lc.channelState.ChanType.IsTaproot() { + return lc.channelState.RemoteNextRevocation + } + + if lc.musigSessions == nil { + return nil + } + return lc.channelState.RemoteNextRevocation } From d3cb968db9d96867bc4566dc966efc4803f7e232 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 31 Aug 2022 18:04:09 -0700 Subject: [PATCH 39/87] lnwallet: add initial support for musig2 handling for commitment dance --- lnwallet/channel.go | 208 +++++++++++------- .../musig2_session.go | 62 +++++- lnwallet/test_utils.go | 9 +- 3 files changed, 198 insertions(+), 81 deletions(-) rename musig2_session.go => lnwallet/musig2_session.go (86%) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index bec08361d97..e0da5e41333 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -229,7 +230,7 @@ func (u updateType) String() string { // the original added HTLC. // // TODO(roasbeef): LogEntry interface?? -// * need to separate attrs for cancel/add/settle/feeupdate +// - need to separate attrs for cancel/add/settle/feeupdate type PaymentDescriptor struct { // RHash is the payment hash for this HTLC. The HTLC can be settled iff // the preimage to this hash is presented. @@ -1038,8 +1039,8 @@ func (s *commitmentChain) hasUnackedCommitment() bool { // // TODO(roasbeef): create lightning package, move commitment and update to // package? -// * also move state machine, separate from lnwallet package -// * possible embed updateLog within commitmentChain. +// - also move state machine, separate from lnwallet package +// - possible embed updateLog within commitmentChain. type updateLog struct { // logIndex is a monotonically increasing integer that tracks the total // number of update entries ever applied to the log. When sending new @@ -1235,18 +1236,18 @@ func compactLogs(ourLog, theirLog *updateLog, // preimages in order to populate their revocation window for the remote party. // // The state machine has for main methods: -// * .SignNextCommitment() -// * Called one one wishes to sign the next commitment, either initiating a -// new state update, or responding to a received commitment. -// * .ReceiveNewCommitment() -// * Called upon receipt of a new commitment from the remote party. If the -// new commitment is valid, then a revocation should immediately be -// generated and sent. -// * .RevokeCurrentCommitment() -// * Revokes the current commitment. Should be called directly after -// receiving a new commitment. -// * .ReceiveRevocation() -// * Processes a revocation from the remote party. If successful creates a +// - .SignNextCommitment() +// - Called one one wishes to sign the next commitment, either initiating a +// new state update, or responding to a received commitment. +// - .ReceiveNewCommitment() +// - Called upon receipt of a new commitment from the remote party. If the +// new commitment is valid, then a revocation should immediately be +// generated and sent. +// - .RevokeCurrentCommitment() +// - Revokes the current commitment. Should be called directly after +// receiving a new commitment. +// - .ReceiveRevocation() +// - Processes a revocation from the remote party. If successful creates a // new defacto broadcastable state. // // See the individual comments within the above methods for further details. @@ -3683,7 +3684,9 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // HTLC's on the commitment transaction. Finally, the new set of pending HTLCs // for the remote party's commitment are also returned. func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, - []channeldb.HTLC, error) { + []channeldb.HTLC, *lnwire.Musig2Nonce, error) { + + // TODO(roasbeef): make return val into struct w/ all the new args lc.Lock() defer lc.Unlock() @@ -3710,7 +3713,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, if unacked || commitPoint == nil { lc.log.Tracef("waiting for remote ack=%v, nil "+ "RemoteNextRevocation: %v", unacked, commitPoint == nil) - return sig, htlcSigs, nil, ErrNoWindow + return sig, htlcSigs, nil, nil, ErrNoWindow } // Determine the last update on the remote log that has been locked in. @@ -3725,7 +3728,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, remoteACKedIndex, lc.localUpdateLog.logIndex, true, nil, nil, ) if err != nil { - return sig, htlcSigs, nil, err + return sig, htlcSigs, nil, nil, err } // Grab the next commitment point for the remote party. This will be @@ -3748,7 +3751,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, remoteACKedIndex, remoteHtlcIndex, keyRing, ) if err != nil { - return sig, htlcSigs, nil, err + return sig, htlcSigs, nil, nil, err } lc.log.Tracef("extending remote chain to height %v, "+ @@ -3765,6 +3768,8 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, }), ) + // TODO(roasbeef): update HLTC batch jobs + // With the commitment view constructed, if there are any HTLC's, we'll // need to generate signatures of each of them for the remote party's // commitment state. We do so in two phases: first we generate and @@ -3779,23 +3784,43 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, &lc.channelState.RemoteChanCfg, newCommitView, ) if err != nil { - return sig, htlcSigs, nil, err + return sig, htlcSigs, nil, nil, err } lc.sigPool.SubmitSignBatch(sigBatch) // While the jobs are being carried out, we'll Sign their version of // the new commitment transaction while we're waiting for the rest of // the HTLC signatures to be processed. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(newCommitView.txn) - rawSig, err := lc.Signer.SignOutputRaw(newCommitView.txn, lc.signDesc) - if err != nil { - close(cancelChan) - return sig, htlcSigs, nil, err - } - sig, err = lnwire.NewSigFromSignature(rawSig) - if err != nil { - close(cancelChan) - return sig, htlcSigs, nil, err + var nextSigningNonce lnwire.Musig2Nonce + if !lc.channelState.ChanType.IsTaproot() { + localSession := lc.musigSessions.LocalSession + partialSig, nextNonce, err := localSession.SignCommit( + newCommitView.txn, + ) + if err != nil { + close(cancelChan) + return sig, htlcSigs, nil, nil, err + } + + nextSigningNonce = *nextNonce + + copy(sig[:], partialSig.Serialize()) + } else { + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only( + newCommitView.txn, + ) + rawSig, err := lc.Signer.SignOutputRaw( + newCommitView.txn, lc.signDesc, + ) + if err != nil { + close(cancelChan) + return sig, htlcSigs, nil, nil, err + } + sig, err = lnwire.NewSigFromSignature(rawSig) + if err != nil { + close(cancelChan) + return sig, htlcSigs, nil, nil, err + } } // We'll need to send over the signatures to the remote party in the @@ -3815,7 +3840,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // jobs. if jobResp.Err != nil { close(cancelChan) - return sig, htlcSigs, nil, jobResp.Err + return sig, htlcSigs, nil, nil, jobResp.Err } htlcSigs = append(htlcSigs, jobResp.Sig) @@ -3826,11 +3851,11 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // can retransmit it if necessary. commitDiff, err := lc.createCommitDiff(newCommitView, sig, htlcSigs) if err != nil { - return sig, htlcSigs, nil, err + return sig, htlcSigs, nil, nil, err } err = lc.channelState.AppendRemoteCommitChain(commitDiff) if err != nil { - return sig, htlcSigs, nil, err + return sig, htlcSigs, nil, nil, err } // TODO(roasbeef): check that one eclair bug @@ -3841,7 +3866,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // latest commitment update. lc.remoteCommitChain.addCommitment(newCommitView) - return sig, htlcSigs, commitDiff.Commitment.Htlcs, nil + return sig, htlcSigs, commitDiff.Commitment.Htlcs, &nextSigningNonce, nil } // ProcessChanSyncMsg processes a ChannelReestablish message sent by the remote @@ -3854,10 +3879,10 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // // One of two message sets will be returned: // -// * CommitSig+Updates: if we have a pending remote commit which they claim to -// have not received -// * RevokeAndAck: if we sent a revocation message that they claim to have -// not received +// - CommitSig+Updates: if we have a pending remote commit which they claim to +// have not received +// - RevokeAndAck: if we sent a revocation message that they claim to have +// not received // // If we detect a scenario where we need to send a CommitSig+Updates, this // method also returns two sets channeldb.CircuitKeys identifying the circuits @@ -3999,7 +4024,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // revocation, but also initiate a state transition to re-sync // them. if lc.OweCommitment() { - commitSig, htlcSigs, _, err := lc.SignNextCommitment() + commitSig, htlcSigs, _, nextNonce, err := lc.SignNextCommitment() switch { // If we signed this state, then we'll accumulate @@ -4011,6 +4036,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( ), CommitSig: commitSig, HtlcSigs: htlcSigs, + RemoteNonce: &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: *nextNonce, + }, }) // If we get a failure due to not knowing their next @@ -4491,7 +4519,7 @@ var _ error = (*InvalidCommitSigError)(nil) // state, then this newly added commitment becomes our current accepted channel // state. func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, - htlcSigs []lnwire.Sig) error { + htlcSigs []lnwire.Sig, nextSigningNonce lnwire.Musig2Nonce) error { lc.Lock() defer lc.Unlock() @@ -4563,6 +4591,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, }), ) + // TODO(roasbeef): update verification below + // Construct the sighash of the commitment transaction corresponding to // this newly proposed state update. localCommitTx := localCommitmentView.txn @@ -4661,7 +4691,10 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, localCommitmentView.sig = commitSig.ToSignatureBytes() lc.localCommitChain.addCommitment(localCommitmentView) - return nil + // At this point, we now have their next signing nonce, so we can + // refresh our local session which allows us to verify more incoming + // commitments. + return lc.musigSessions.LocalSession.Refresh(nextSigningNonce) } // IsChannelClean returns true if neither side has pending commitments, neither @@ -4835,6 +4868,17 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c &lc.channelState.FundingOutpoint, ) + // TODO(roasbeef): refresh session here + + // We've now accepted+revoked a new commitment, so we'll send the + // remote party another verification nonce they can use to generate new + // commitments. + musigSession := lc.musigSessions + nextVerificationNonce := musigSession.LocalSession.VerificationNonce() + revocationMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: nextVerificationNonce, + } + return revocationMsg, newCommitment.Htlcs, nil } @@ -4846,14 +4890,14 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c // commitment, and a log compaction is attempted. // // The returned values correspond to: -// 1. The forwarding package corresponding to the remote commitment height -// that was revoked. -// 2. The PaymentDescriptor of any Add HTLCs that were locked in by this -// revocation. -// 3. The PaymentDescriptor of any Settle/Fail HTLCs that were locked in by -// this revocation. -// 4. The set of HTLCs present on the current valid commitment transaction -// for the remote party. +// 1. The forwarding package corresponding to the remote commitment height +// that was revoked. +// 2. The PaymentDescriptor of any Add HTLCs that were locked in by this +// revocation. +// 3. The PaymentDescriptor of any Settle/Fail HTLCs that were locked in by +// this revocation. +// 4. The set of HTLCs present on the current valid commitment transaction +// for the remote party. func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( *channeldb.FwdPkg, []*PaymentDescriptor, []*PaymentDescriptor, []channeldb.HTLC, error) { @@ -5063,6 +5107,15 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( return nil, nil, nil, nil, err } + // Now that we have a new verification nonce from them, we can refresh + // our remote musig2 session which allows us to create another state. + err = lc.musigSessions.RemoteSession.Refresh( + revMsg.LocalNonce.Musig2Nonce, + ) + if err != nil { + return nil, nil, nil, nil, err + } + // At this point, the revocation has been accepted, and we've rotated // the current revocation key+hash for the remote party. Therefore we // sync now to ensure the revocation producer state is consistent with @@ -5387,20 +5440,21 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err // is invalid, an error is returned. // // The additional arguments correspond to: -// * sourceRef: specifies the location of the Add HTLC within a forwarding -// package that this HTLC is settling. Every Settle fails exactly one Add, -// so this should never be empty in practice. // -// * destRef: specifies the location of the Settle HTLC within another -// channel's forwarding package. This value can be nil if the corresponding -// Add HTLC was never locked into an outgoing commitment txn, or this -// HTLC does not originate as a response from the peer on the outgoing -// link, e.g. on-chain resolutions. +// - sourceRef: specifies the location of the Add HTLC within a forwarding +// package that this HTLC is settling. Every Settle fails exactly one Add, +// so this should never be empty in practice. // -// * closeKey: identifies the circuit that should be deleted after this Settle -// HTLC is included in a commitment txn. This value should only be nil if -// the HTLC was settled locally before committing a circuit to the circuit -// map. +// - destRef: specifies the location of the Settle HTLC within another +// channel's forwarding package. This value can be nil if the corresponding +// Add HTLC was never locked into an outgoing commitment txn, or this +// HTLC does not originate as a response from the peer on the outgoing +// link, e.g. on-chain resolutions. +// +// - closeKey: identifies the circuit that should be deleted after this Settle +// HTLC is included in a commitment txn. This value should only be nil if +// the HTLC was settled locally before committing a circuit to the circuit +// map. // // NOTE: It is okay for sourceRef, destRef, and closeKey to be nil when unit // testing the wallet. @@ -5497,20 +5551,21 @@ func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, htlcIndex uint6 // _incoming_ HTLC. // // The additional arguments correspond to: -// * sourceRef: specifies the location of the Add HTLC within a forwarding -// package that this HTLC is failing. Every Fail fails exactly one Add, so -// this should never be empty in practice. // -// * destRef: specifies the location of the Fail HTLC within another channel's -// forwarding package. This value can be nil if the corresponding Add HTLC -// was never locked into an outgoing commitment txn, or this HTLC does not -// originate as a response from the peer on the outgoing link, e.g. -// on-chain resolutions. +// - sourceRef: specifies the location of the Add HTLC within a forwarding +// package that this HTLC is failing. Every Fail fails exactly one Add, so +// this should never be empty in practice. +// +// - destRef: specifies the location of the Fail HTLC within another channel's +// forwarding package. This value can be nil if the corresponding Add HTLC +// was never locked into an outgoing commitment txn, or this HTLC does not +// originate as a response from the peer on the outgoing link, e.g. +// on-chain resolutions. // -// * closeKey: identifies the circuit that should be deleted after this Fail -// HTLC is included in a commitment txn. This value should only be nil if -// the HTLC was failed locally before committing a circuit to the circuit -// map. +// - closeKey: identifies the circuit that should be deleted after this Fail +// HTLC is included in a commitment txn. This value should only be nil if +// the HTLC was failed locally before committing a circuit to the circuit +// map. // // NOTE: It is okay for sourceRef, destRef, and closeKey to be nil when unit // testing the wallet. @@ -5672,7 +5727,7 @@ func (lc *LightningChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress // AbsoluteThawHeight determines a frozen channel's absolute thaw height. If // the channel is not frozen, then 0 is returned. // -// An error is returned if the channel is penidng, or is an unconfirmed zero +// An error is returned if the channel is pending, or is an unconfirmed zero // conf channel. func (lc *LightningChannel) AbsoluteThawHeight() (uint32, error) { return lc.channelState.AbsoluteThawHeight() @@ -5686,6 +5741,9 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { localCommit := lc.channelState.LocalCommitment commitTx := localCommit.CommitTx.Copy() + // TODO(roasbeef): parametrize... + // * also want the full R sig as well + theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) if err != nil { return nil, err @@ -7589,5 +7647,7 @@ func (lc *LightningChannel) InitRemoteMusigNonces(nonces *MusigNoncePair) error return fmt.Errorf("unable to gen musig session: %w", err) } + lc.nextNoncePair = nil + return nil } diff --git a/musig2_session.go b/lnwallet/musig2_session.go similarity index 86% rename from musig2_session.go rename to lnwallet/musig2_session.go index 7804f34d3ba..b659ff1f9d1 100644 --- a/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -109,7 +109,10 @@ type MusigSession struct { signerKeys []*btcec.PublicKey // remoteKey... - remoteKey *btcec.PublicKey + remoteKey keychain.KeyDescriptor + + // localKey... + localKey keychain.KeyDescriptor // signer... signer input.MuSig2Signer @@ -166,7 +169,8 @@ func NewMusigSession(noncePair MusigNoncePair, return &MusigSession{ nonces: noncePair, - remoteKey: remoteKey.PubKey, + remoteKey: remoteKey, + localKey: localKey, session: session, combinedNonce: combinedNonce, inputTxOut: inputTxOut, @@ -235,11 +239,63 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.Pu m.nextNonces = &nextNonces + // TODO(roasbeef): clean up prior session once new created? + return NewMusigPartialSig( sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, ), &nextSigningNonce.PubNonce, nil } +// Refresh... +func (m *MusigSession) Refresh(nextNonce [musig2.PubNonceSize]byte) error { + // At this point we should have a next nonce, otherwise this operation + // is undefined as we haven't yet used our current nonce. + if m.nextNonces == nil { + // TODO(roasbeef): proper error + return fmt.Errorf("no next nonce") + } + + // Now that we know we have the nonce we need, we can complete the + // nonce pair. + if m.remoteCommit { + m.nextNonces.LocalNonce = &musig2.Nonces{ + PubNonce: nextNonce, + } + } else { + m.nextNonces.RemoteNonce = &musig2.Nonces{ + PubNonce: nextNonce, + } + } + + // Now we'll just re-create ourselves entirely given this new + // information. We'll also clean up the old session since we don't need + // it any longer. + // + // TODO(roasbeef): can't actually clean up here? but need the stateless + // signer thing? + defer m.signer.MuSig2Cleanup(m.session.SessionID) + + var err error + m, err = NewMusigSession( + *m.nextNonces, m.localKey, m.remoteKey, m.signer, m.inputTxOut, + m.remoteCommit, + ) + if err != nil { + return err + } + + return nil +} + +// VerificationNonce... +func (m *MusigSession) VerificationNonce() [musig2.PubNonceSize]byte { + if m.remoteCommit { + return m.nonces.RemoteNonce.PubNonce + } else { + return m.nonces.LocalNonce.PubNonce + } +} + // TODO(roasbeef): re hot signatures, maybe would re-use the state less signing // thing after all? // @@ -271,7 +327,7 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, return nil, err } - if !partialSig.Verify(sigHash, m.remoteKey) { + if !partialSig.Verify(sigHash, m.remoteKey.PubKey) { return nil, fmt.Errorf("invalid partial commit sig") } diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index f1209d70148..6020de53dbc 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -502,11 +502,12 @@ func calcStaticFee(chanType channeldb.ChannelType, numHTLCs int) btcutil.Amount // pending updates. This method is useful when testing interactions between two // live state machines. func ForceStateTransition(chanA, chanB *LightningChannel) error { - aliceSig, aliceHtlcSigs, _, err := chanA.SignNextCommitment() + aliceSig, aliceHtlcSigs, _, aliceNonce, err := chanA.SignNextCommitment() if err != nil { return err } - if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil { + err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs, *aliceNonce) + if err != nil { return err } @@ -514,7 +515,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if err != nil { return err } - bobSig, bobHtlcSigs, _, err := chanB.SignNextCommitment() + bobSig, bobHtlcSigs, _, bobNonce, err := chanB.SignNextCommitment() if err != nil { return err } @@ -522,7 +523,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if _, _, _, _, err := chanA.ReceiveRevocation(bobRevocation); err != nil { return err } - if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil { + if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs, *bobNonce); err != nil { return err } From 33f3b444fba46d8d32a60bde46ae0fbe58e45255 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 16 Nov 2022 18:28:26 -0800 Subject: [PATCH 40/87] input: fix taproot local/remote scripts --- input/script_utils.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index 7033aa5e71a..d09ba541066 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -1103,6 +1103,7 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, builder.AddData(schnorr.SerializePubKey(selfKey)) builder.AddOp(txscript.OP_CHECKSIG) builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) builder.AddOp(txscript.OP_DROP) delayScript, err := builder.Script() @@ -1358,7 +1359,7 @@ func TaprootCommitScriptToRemote(combinedFundingKey, // outputs. builder := txscript.NewScriptBuilder() builder.AddData(schnorr.SerializePubKey(remoteKey)) - builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddOp(txscript.OP_CHECKSIG) builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) delayScript, err := builder.Script() @@ -1375,7 +1376,7 @@ func TaprootCommitScriptToRemote(combinedFundingKey, // Now that we have our root, we can arrive at the final output script // by tweaking the internal key with this root. toRemoteOutputKey := txscript.ComputeTaprootOutputKey( - remoteKey, tapScriptRoot[:], + combinedFundingKey, tapScriptRoot[:], ) return toRemoteOutputKey, nil From bb085b04647a32430ca98ac49df07f934f160579 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 16 Nov 2022 18:29:39 -0800 Subject: [PATCH 41/87] lnwallet: use tweaked keys for taproot anchor outputs --- lnwallet/channel.go | 41 ++++++++++++++++++++++++++++++++++------- lnwallet/commitment.go | 12 ++++++------ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index e0da5e41333..076a815a3f0 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5951,7 +5951,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si } anchorResolution, err := NewAnchorResolution( - chanState, commitTxBroadcast, + chanState, commitTxBroadcast, keyRing, ) if err != nil { return nil, err @@ -6648,7 +6648,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, } anchorResolution, err := NewAnchorResolution( - chanState, commitTx, + chanState, commitTx, keyRing, ) if err != nil { return nil, err @@ -6832,7 +6832,7 @@ type AnchorResolutions struct { // NewAnchorResolutions returns a set of anchor resolutions wrapped in the // struct AnchorResolutions. Because we have no view on the mempool, we can -// only blindly anchor all of these txes down. Caller needs to check the +// only blindly anchor all of these txes down. The caller needs to check the // returned values against nil to decide whether there exists an anchor // resolution for local/remote/pending remote commitment txes. func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, @@ -6841,11 +6841,25 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, lc.Lock() defer lc.Unlock() - resolutions := &AnchorResolutions{} + // TODO(roasbeef): store revocation state along the commits? + + var resolutions AnchorResolutions // Add anchor for local commitment tx, if any. + revocation, err := lc.channelState.RevocationProducer.AtIndex( + lc.currentHeight, + ) + if err != nil { + return nil, err + } + localCommitPoint := input.ComputeCommitmentPoint(revocation[:]) + localKeyRing := DeriveCommitmentKeys( + localCommitPoint, true, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, + ) localRes, err := NewAnchorResolution( lc.channelState, lc.channelState.LocalCommitment.CommitTx, + localKeyRing, ) if err != nil { return nil, err @@ -6853,8 +6867,14 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, resolutions.Local = localRes // Add anchor for remote commitment tx, if any. + remoteKeyRing := DeriveCommitmentKeys( + lc.channelState.RemoteCurrentRevocation, false, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, + ) remoteRes, err := NewAnchorResolution( lc.channelState, lc.channelState.RemoteCommitment.CommitTx, + remoteKeyRing, ) if err != nil { return nil, err @@ -6868,9 +6888,15 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, } if remotePendingCommit != nil { + pendingRemoteKeyRing := DeriveCommitmentKeys( + lc.channelState.RemoteNextRevocation, false, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, + ) remotePendingRes, err := NewAnchorResolution( lc.channelState, remotePendingCommit.Commitment.CommitTx, + pendingRemoteKeyRing, ) if err != nil { return nil, err @@ -6878,13 +6904,14 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, resolutions.RemotePending = remotePendingRes } - return resolutions, nil + return &resolutions, nil } // NewAnchorResolution returns the information that is required to sweep the // local anchor. func NewAnchorResolution(chanState *channeldb.OpenChannel, - commitTx *wire.MsgTx) (*AnchorResolution, error) { + commitTx *wire.MsgTx, + keyRing *CommitmentKeyRing) (*AnchorResolution, error) { // Return nil resolution if the channel has no anchors. if !chanState.ChanType.HasAnchors() { @@ -6894,7 +6921,7 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, // Derive our local anchor script. localAnchor, _, err := CommitScriptAnchors( chanState.ChanType, &chanState.LocalChanCfg, - &chanState.RemoteChanCfg, + &chanState.RemoteChanCfg, keyRing, ) if err != nil { return nil, err diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index ddc1d6e3163..59a006c6fa7 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -515,8 +515,8 @@ func HtlcSuccessFee(chanType channeldb.ChannelType, // CommitScriptAnchors return the scripts to use for the local and remote // anchor. func CommitScriptAnchors(chanType channeldb.ChannelType, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, - *ScriptInfo, error) { + localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + keyRing *CommitmentKeyRing) (*ScriptInfo, *ScriptInfo, error) { var ( anchorScript func(key *btcec.PublicKey) (*ScriptInfo, error) @@ -551,10 +551,10 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, local bool) *btcec.PublicKey { if local { - return cfg.DelayBasePoint.PubKey + return keyRing.ToLocalKey } - return cfg.PaymentBasePoint.PubKey + return keyRing.ToRemoteKey } // For normal channels we'll use the multi-sig keys since those are @@ -606,7 +606,7 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, // with, and abstracts the various ways of constructing commitment // transactions. type CommitmentBuilder struct { - // chanState is the underlying channels's state struct, used to + // chanState is the underlying channel's state struct, used to // determine the type of channel we are dealing with, and relevant // parameters. chanState *channeldb.OpenChannel @@ -923,7 +923,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // If this channel type has anchors, we'll also add those. if chanType.HasAnchors() { localAnchor, remoteAnchor, err := CommitScriptAnchors( - chanType, localChanCfg, remoteChanCfg, + chanType, localChanCfg, remoteChanCfg, keyRing, ) if err != nil { return nil, err From 2b932282498543ca74e7ba91a66759bb5e53d319 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 16 Nov 2022 18:31:28 -0800 Subject: [PATCH 42/87] lnwallet: introduce new CommitSigs struct for sign/recv commit --- lnwallet/channel.go | 84 ++++-- lnwallet/channel_test.go | 532 ++++++++++++++++++---------------- lnwallet/test_utils.go | 8 +- lnwallet/transactions_test.go | 18 +- 4 files changed, 347 insertions(+), 295 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 076a815a3f0..04c72fb9f23 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3672,6 +3672,28 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, return nil } +// CommitSig... +type CommitSigs struct { + // CommitSig... + CommitSig lnwire.Sig + + // HtlcSigs... + HtlcSigs []lnwire.Sig + + // NextSignerNonce... + NextSignerNonce *lnwire.Musig2Nonce +} + +// NewCommitState wraps the various signatures needed to properly +// propose/accept a new commitment state. This includes the signer's nonce for +// musig2 channels. +type NewCommitState struct { + *CommitSigs + + // PendingHTLCs... + PendingHTLCs []channeldb.HTLC +} + // SignNextCommitment signs a new commitment which includes any previous // unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs // committed in previous commitment updates. Signing a new commitment @@ -3683,8 +3705,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // any). The HTLC signatures are sorted according to the BIP 69 order of the // HTLC's on the commitment transaction. Finally, the new set of pending HTLCs // for the remote party's commitment are also returned. -func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, - []channeldb.HTLC, *lnwire.Musig2Nonce, error) { +func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // TODO(roasbeef): make return val into struct w/ all the new args @@ -3713,7 +3734,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, if unacked || commitPoint == nil { lc.log.Tracef("waiting for remote ack=%v, nil "+ "RemoteNextRevocation: %v", unacked, commitPoint == nil) - return sig, htlcSigs, nil, nil, ErrNoWindow + return nil, ErrNoWindow } // Determine the last update on the remote log that has been locked in. @@ -3728,7 +3749,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, remoteACKedIndex, lc.localUpdateLog.logIndex, true, nil, nil, ) if err != nil { - return sig, htlcSigs, nil, nil, err + return nil, err } // Grab the next commitment point for the remote party. This will be @@ -3751,7 +3772,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, remoteACKedIndex, remoteHtlcIndex, keyRing, ) if err != nil { - return sig, htlcSigs, nil, nil, err + return nil, err } lc.log.Tracef("extending remote chain to height %v, "+ @@ -3784,7 +3805,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, &lc.channelState.RemoteChanCfg, newCommitView, ) if err != nil { - return sig, htlcSigs, nil, nil, err + return nil, err } lc.sigPool.SubmitSignBatch(sigBatch) @@ -3799,7 +3820,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, ) if err != nil { close(cancelChan) - return sig, htlcSigs, nil, nil, err + return nil, err } nextSigningNonce = *nextNonce @@ -3814,12 +3835,12 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, ) if err != nil { close(cancelChan) - return sig, htlcSigs, nil, nil, err + return nil, err } sig, err = lnwire.NewSigFromSignature(rawSig) if err != nil { close(cancelChan) - return sig, htlcSigs, nil, nil, err + return nil, err } } @@ -3840,7 +3861,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // jobs. if jobResp.Err != nil { close(cancelChan) - return sig, htlcSigs, nil, nil, jobResp.Err + return nil, jobResp.Err } htlcSigs = append(htlcSigs, jobResp.Sig) @@ -3851,11 +3872,11 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // can retransmit it if necessary. commitDiff, err := lc.createCommitDiff(newCommitView, sig, htlcSigs) if err != nil { - return sig, htlcSigs, nil, nil, err + return nil, err } err = lc.channelState.AppendRemoteCommitChain(commitDiff) if err != nil { - return sig, htlcSigs, nil, nil, err + return nil, err } // TODO(roasbeef): check that one eclair bug @@ -3866,7 +3887,14 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // latest commitment update. lc.remoteCommitChain.addCommitment(newCommitView) - return sig, htlcSigs, commitDiff.Commitment.Htlcs, &nextSigningNonce, nil + return &NewCommitState{ + CommitSigs: &CommitSigs{ + CommitSig: sig, + HtlcSigs: htlcSigs, + NextSignerNonce: nextSigningNonce, + }, + PendingHTLCs: commitDiff.Commitment.Htlcs, + }, nil } // ProcessChanSyncMsg processes a ChannelReestablish message sent by the remote @@ -4024,22 +4052,26 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // revocation, but also initiate a state transition to re-sync // them. if lc.OweCommitment() { - commitSig, htlcSigs, _, nextNonce, err := lc.SignNextCommitment() + newCommit, err := lc.SignNextCommitment() switch { // If we signed this state, then we'll accumulate // another update to send over. case err == nil: - updates = append(updates, &lnwire.CommitSig{ + commitSig := &lnwire.CommitSig{ ChanID: lnwire.NewChanIDFromOutPoint( &lc.channelState.FundingOutpoint, ), - CommitSig: commitSig, - HtlcSigs: htlcSigs, - RemoteNonce: &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: *nextNonce, - }, - }) + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + } + if newCommit.NextSignerNonce != nil { + commitSig.RemoteNonce = &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: *newCommit.NextSignerNonce, + } + } + + updates = append(updates, commitSig) // If we get a failure due to not knowing their next // point, then this is fine as they'll either send @@ -4518,8 +4550,7 @@ var _ error = (*InvalidCommitSigError)(nil) // to our local commitment chain. Once we send a revocation for our prior // state, then this newly added commitment becomes our current accepted channel // state. -func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, - htlcSigs []lnwire.Sig, nextSigningNonce lnwire.Musig2Nonce) error { +func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { lc.Lock() defer lc.Unlock() @@ -4616,7 +4647,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, leaseExpiry = lc.channelState.ThawHeight } verifyJobs, err := genHtlcSigValidationJobs( - localCommitmentView, keyRing, htlcSigs, + localCommitmentView, keyRing, commitSigs.HtlcSigs, lc.channelState.ChanType, lc.channelState.IsInitiator, leaseExpiry, &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, @@ -4633,7 +4664,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // signature. verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey - cSig, err := commitSig.ToSignature() + cSig, err := commitSigs.CommitSig.ToSignature() if err != nil { return err } @@ -4648,7 +4679,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, localCommitTx.Serialize(&txBytes) return &InvalidCommitSigError{ commitHeight: nextHeight, - commitSig: commitSig.ToSignatureBytes(), + commitSig: commitSigs.CommitSig.ToSignatureBytes(), sigHash: sigHash, commitTx: txBytes.Bytes(), } @@ -4689,6 +4720,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // The signature checks out, so we can now add the new commitment to // our local commitment chain. localCommitmentView.sig = commitSig.ToSignatureBytes() + localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() lc.localCommitChain.addCommitment(localCommitmentView) // At this point, we now have their next signing nonce, so we can diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 25aa1aa0b92..04759603ffd 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -94,13 +94,13 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // we expect the messages to be ordered, Bob will receive the HTLC we // just sent before he receives this signature, so the signature will // cover the HTLC. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Bob receives this signature message, and checks that this covers the // state he has in his remote log. This includes the HTLC just sent // from Alice. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") // Bob revokes his prior commitment given to him by Alice, since he now @@ -112,7 +112,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // This signature will cover the HTLC, since Bob will first send the // revocation just created. The revocation also acks every received // HTLC up to the point where Alice sent here signature. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Alice then processes this revocation, sending her own revocation for @@ -132,7 +132,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // Alice then processes bob's signature, and since she just received // the revocation, she expect this signature to cover everything up to // the point where she sent her signature, including the HTLC. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") // Alice then generates a revocation for bob. @@ -216,14 +216,14 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } - bobSig2, bobHtlcSigs2, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign settle commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig2, bobHtlcSigs2) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") aliceRevocation2, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to generate revocation") - aliceSig2, aliceHtlcSigs2, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign new commitment") fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation2) @@ -237,7 +237,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { "should forward none", len(fwdPkg.SettleFails)) } - err = bobChannel.ReceiveNewCommitment(aliceSig2, aliceHtlcSigs2) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") bobRevocation2, _, err := bobChannel.RevokeCurrentCommitment() @@ -316,8 +316,8 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // // TODO(roasbeef): write higher level framework to exercise various states of // the state machine -// * DSL language perhaps? -// * constructed via input/output files +// - DSL language perhaps? +// - constructed via input/output files func TestSimpleAddSettleWorkflow(t *testing.T) { t.Parallel() @@ -335,17 +335,18 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { // The full state transition of this test is: // // Alice Bob -// -----add------> -// -----sig------> -// <----rev------- -// <----sig------- -// -----rev------> -// <---settle----- -// <----sig------- -// -----rev------> -// *alice dies* -// <----add------- -// x----sig------- +// +// -----add------> +// -----sig------> +// <----rev------- +// <----sig------- +// -----rev------> +// <---settle----- +// <----sig------- +// -----rev------> +// *alice dies* +// <----add------- +// x----sig------- // // The last sig will be rejected if addCommitHeightLocal is not set for the // initial add that Alice sent. This test checks that this behavior does @@ -386,10 +387,10 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { // Bob should send a commitment signature to Alice. // <----sig------ - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) // Alice should reply with a revocation. @@ -420,12 +421,12 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { // Bob should now send a commitment signature to Alice. // <----sig----- - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) // Alice should accept the commitment. Previously she would // force close here. - err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -567,10 +568,10 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { // tie-breaking for commitment sorting won't affect the commitment // signed by Alice because received HTLC scripts commit to the CLTV // directly, so the outputs will have different scriptPubkeys. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign alice's commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() @@ -582,19 +583,20 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { // the offered HTLC scripts he adds for Alice will need to have the // tie-breaking applied because the CLTV is not committed, but instead // implicit via the construction of the second-level transactions. - bobSig, bobHtlcSigs, bobHtlcs, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign bob's commitment") - if len(bobHtlcs) != numHtlcs { - t.Fatalf("expected %d htlcs, got: %v", numHtlcs, len(bobHtlcs)) + if len(bobNewCommit.PendingHTLCs) != numHtlcs { + t.Fatalf("expected %d htlcs, got: %v", numHtlcs, + len(bobNewCommit.PendingHTLCs)) } // Ensure that our HTLCs appear in the reverse order from which they // were added by inspecting each's outpoint index. We expect the output // indexes to be in descending order, i.e. the first HTLC added had the // highest CLTV and should end up last. - lastIndex := bobHtlcs[0].OutputIndex - for i, htlc := range bobHtlcs[1:] { + lastIndex := bobNewCommit.PendingHTLCs[0].OutputIndex + for i, htlc := range bobNewCommit.PendingHTLCs[1:] { if htlc.OutputIndex >= lastIndex { t.Fatalf("htlc %d output index %d is not descending", i, htlc.OutputIndex) @@ -628,7 +630,7 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { // Finally, have Alice validate the signatures to ensure that she is // expecting the signatures in the proper order. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive bob's commitment") } @@ -1411,17 +1413,19 @@ func TestHTLCSigNumber(t *testing.T) { aboveDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") - if len(aliceHtlcSigs) != 2 { + if len(aliceNewCommit.HtlcSigs) != 2 { t.Fatalf("expected 2 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } // Now discard one signature from the htlcSig slice. Bob should reject // the commitment because of this. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs[1:]) + aliceNewCommitCopy := *aliceNewCommit + aliceNewCommitCopy.HtlcSigs = aliceNewCommitCopy.HtlcSigs[1:] + err = bobChannel.ReceiveNewCommitment(aliceNewCommitCopy.CommitSigs) if err == nil { t.Fatalf("Expected Bob to reject signatures") } @@ -1433,17 +1437,19 @@ func TestHTLCSigNumber(t *testing.T) { aliceChannel, bobChannel, cleanUp = createChanWithHTLC(aboveDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") - if len(aliceHtlcSigs) != 1 { + if len(aliceNewCommit.HtlcSigs) != 1 { t.Fatalf("expected 1 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } // Now just give Bob an empty htlcSig slice. He should reject the // commitment because of this. - err = bobChannel.ReceiveNewCommitment(aliceSig, []lnwire.Sig{}) + aliceCommitCopy := *aliceNewCommit.CommitSigs + aliceCommitCopy.HtlcSigs = []lnwire.Sig{} + err = bobChannel.ReceiveNewCommitment(&aliceCommitCopy) if err == nil { t.Fatalf("Expected Bob to reject signatures") } @@ -1454,17 +1460,17 @@ func TestHTLCSigNumber(t *testing.T) { aliceChannel, bobChannel, cleanUp = createChanWithHTLC(belowDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") // Since the HTLC is below Bob's dust limit, Alice won't need to send // any signatures for this HTLC. - if len(aliceHtlcSigs) != 0 { + if len(aliceNewCommit.HtlcSigs) != 0 { t.Fatalf("expected no htlc sigs, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "Bob failed receiving commitment") // ================================================================ @@ -1473,17 +1479,17 @@ func TestHTLCSigNumber(t *testing.T) { aliceChannel, bobChannel, cleanUp = createChanWithHTLC(aboveDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") // Since the HTLC is above Bob's dust limit, Alice should send a // signature for this HTLC. - if len(aliceHtlcSigs) != 1 { + if len(aliceNewCommit.PendingHTLCs) != 1 { t.Fatalf("expected 1 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.PendingHTLCs)) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "Bob failed receiving commitment") // ==================================================================== @@ -1496,20 +1502,22 @@ func TestHTLCSigNumber(t *testing.T) { // Alice should produce only one signature, since one HTLC is below // dust. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") - if len(aliceHtlcSigs) != 1 { + if len(aliceNewCommit.HtlcSigs) != 1 { t.Fatalf("expected 1 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } // Add an extra signature. - aliceHtlcSigs = append(aliceHtlcSigs, aliceHtlcSigs[0]) + aliceNewCommit.HtlcSigs = append( + aliceNewCommit.HtlcSigs, aliceNewCommit.HtlcSigs[0], + ) // Bob should reject these signatures since they don't match the number // of HTLCs above dust. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err == nil { t.Fatalf("Expected Bob to reject signatures") } @@ -2257,13 +2265,13 @@ func TestUpdateFeeFail(t *testing.T) { // Alice sends signature for commitment that does not cover any fee // update. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Bob verifies this commit, meaning that he checks that it is // consistent everything he has received. This should fail, since he got // the fee update, but Alice never sent it. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err == nil { t.Fatalf("expected bob to fail receiving alice's signature") } @@ -2306,15 +2314,15 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { } // Alice signs a commitment, and sends this to bob. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // At the same time, Bob signs a commitment. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommits, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // ...that Alice receives. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommits.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") // Now let Bob receive the fee update + commitment that Alice sent. @@ -2325,7 +2333,7 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { // Bob receives this signature message, and verifies that it is // consistent with the state he had for Alice, including the received // HTLC and fee update. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") if chainfee.SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee { @@ -2383,13 +2391,13 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Alice signs a commitment, which will cover everything sent to Bob // (the HTLC and the fee update), and everything acked by Bob (nothing // so far). - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Bob receives this signature message, and verifies that it is // consistent with the state he had for Alice, including the received // HTLC and fee update. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") if chainfee.SatPerKWeight( @@ -2413,7 +2421,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Bob commits to all updates he has received from Alice. This includes // the HTLC he received, and the fee update. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Alice receives the revocation of the old one, and can now assume @@ -2424,7 +2432,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Alice receives new signature from Bob, and assumes this covers the // changes. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") if chainfee.SatPerKWeight( @@ -2493,12 +2501,12 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob commits to every change he has sent since last time (none). He // does not commit to the received HTLC and fee update, since Alice // cannot know if he has received them. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Alice receives this signature message, and verifies that it is // consistent with the remote state, not including any of the updates. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") // Alice can revoke the prior commitment she had, this will ack @@ -2514,12 +2522,12 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Alice will sign next commitment. Since she sent the revocation, she // also ack'ed everything received, but in this case this is nothing. // Since she sent the two updates, this signature will cover those two. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Bob gets the signature for the new commitment from Alice. He assumes // this covers everything received from alice, including the two updates. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") if chainfee.SatPerKWeight( @@ -2544,7 +2552,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob will send a new signature, which will cover what he just acked: // the HTLC and fee update. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Alice receives revocation from Bob, and can now be sure that Bob @@ -2554,7 +2562,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Alice will receive the signature from Bob, which will cover what was // just acked by his revocation. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") if chainfee.SatPerKWeight( @@ -2636,7 +2644,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Alice signs a commitment, which will cover everything sent to Bob // (the HTLC and the fee update), and everything acked by Bob (nothing // so far). - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") bobChannel.ReceiveUpdateFee(fee1) @@ -2646,7 +2654,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Bob receives this signature message, and verifies that it is // consistent with the state he had for Alice, including the received // HTLC and fee update. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") if chainfee.SatPerKWeight( @@ -2682,7 +2690,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Bob commits to all updates he has received from Alice. This includes // the HTLC he received, and the fee update. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Alice receives the revocation of the old one, and can now assume that @@ -2693,7 +2701,8 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Alice receives new signature from Bob, and assumes this covers the // changes. - if err := aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil { + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) + if err != nil { t.Fatalf("alice unable to process bob's new commitment: %v", err) } @@ -2991,7 +3000,7 @@ func TestChanSyncOweCommitment(t *testing.T) { // Now we'll begin the core of the test itself. Alice will extend a new // commitment to Bob, but the connection drops before Bob can process // it. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Bob doesn't get this message so upon reconnection, they need to @@ -3058,20 +3067,20 @@ func TestChanSyncOweCommitment(t *testing.T) { t.Fatalf("expected a CommitSig message, instead have %v", spew.Sdump(aliceMsgsToSend[4])) } - if commitSigMsg.CommitSig != aliceSig { + if commitSigMsg.CommitSig != aliceNewCommit.CommitSig { t.Fatalf("commit sig msgs don't match: expected %x got %x", - aliceSig, commitSigMsg.CommitSig) + aliceNewCommit.CommitSig, commitSigMsg.CommitSig) } - if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) { + if len(commitSigMsg.HtlcSigs) != len(aliceNewCommit.HtlcSigs) { t.Fatalf("wrong number of htlc sigs: expected %v, got %v", - len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs)) + len(aliceNewCommit.HtlcSigs), + len(commitSigMsg.HtlcSigs)) } for i, htlcSig := range commitSigMsg.HtlcSigs { - if htlcSig != aliceHtlcSigs[i] { + if htlcSig != aliceNewCommit.HtlcSigs[i] { t.Fatalf("htlc sig msgs don't match: "+ "expected %x got %x", - aliceHtlcSigs[i], - htlcSig) + aliceNewCommit.HtlcSigs[i], htlcSig) } } } @@ -3101,15 +3110,15 @@ func TestChanSyncOweCommitment(t *testing.T) { // At this point, we should be able to resume the prior state update // without any issues, resulting in Alice settling the 3 htlc's, and // adding one of her own. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -3250,12 +3259,13 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) { t.Fatalf("unable to settle htlc: %v", err) } - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("unable to sign commitment: %v", err) } - - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment( + aliceNewCommit.CommitSigs, + ) if err != nil { t.Fatalf("unable to receive commitment: %v", err) } @@ -3281,16 +3291,17 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) { require.NoError(t, err, "unable to restart bob") // Bob signs the commitment he owes. - bobCommit, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // This commitment is expected to contain no htlcs anymore. - if len(bobHtlcSigs) != 0 { - t.Fatalf("no htlcs expected, but got %v", len(bobHtlcSigs)) + if len(bobNewCommit.HtlcSigs) != 0 { + t.Fatalf("no htlcs expected, but got %v", + len(bobNewCommit.HtlcSigs)) } // Get Alice to revoke and trigger Bob to compact his logs. - err = aliceChannel.ReceiveNewCommitment(bobCommit, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -3356,19 +3367,19 @@ func TestChanSyncOweRevocation(t *testing.T) { // // Alice signs the next state, then Bob receives and sends his // revocation message. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") // At this point, we'll simulate the connection breaking down by Bob's @@ -3507,16 +3518,16 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // Progressing the exchange: Alice will send her signature, Bob will // receive, send a revocation and also a signature for Alice's state. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") // Bob generates the revoke and sig message, but the messages don't // reach Alice before the connection dies. bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") // If we now attempt to resync, then Alice should conclude that she @@ -3558,19 +3569,22 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { t.Fatalf("expected bob to re-send commit sig, instead sending: %v", spew.Sdump(bobMsgsToSend[1])) } - if bobReCommitSigMsg.CommitSig != bobSig { + if bobReCommitSigMsg.CommitSig != bobNewCommit.CommitSig { t.Fatalf("commit sig msgs don't match: expected %x got %x", - bobSig, bobReCommitSigMsg.CommitSig) + bobNewCommit.CommitSigs.CommitSig, + bobReCommitSigMsg.CommitSig) } - if len(bobReCommitSigMsg.HtlcSigs) != len(bobHtlcSigs) { + if len(bobReCommitSigMsg.HtlcSigs) != len(bobNewCommit.HtlcSigs) { t.Fatalf("wrong number of htlc sigs: expected %v, got %v", - len(bobHtlcSigs), len(bobReCommitSigMsg.HtlcSigs)) + len(bobNewCommit.HtlcSigs), + len(bobReCommitSigMsg.HtlcSigs)) } for i, htlcSig := range bobReCommitSigMsg.HtlcSigs { - if htlcSig != aliceHtlcSigs[i] { + if htlcSig != bobNewCommit.HtlcSigs[i] { t.Fatalf("htlc sig msgs don't match: "+ "expected %x got %x", - bobHtlcSigs[i], htlcSig) + htlcSig, + bobNewCommit.HtlcSigs[i]) } } } @@ -3589,8 +3603,8 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // messages, and send her final revocation. _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) - require.NoError(t, err, "alice unable to rev bob's commitment") + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) + require.NoError(t, err, "alice unable to recv bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) @@ -3653,10 +3667,10 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { require.NoError(t, err, "unable to recv bob's htlc") // Bob signs the new state update, and sends the signature to Alice. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") // Alice revokes her current state, but doesn't immediately send a @@ -3677,9 +3691,9 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // Progressing the exchange: Alice will send her signature, with Bob // processing the new state locally. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") // Bob then sends his revocation message, but before Alice can process @@ -3775,9 +3789,10 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // message to Bob. _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment( - bobSigMsg.CommitSig, bobSigMsg.HtlcSigs, - ) + err = aliceChannel.ReceiveNewCommitment(&CommitSigs{ + CommitSig: bobSigMsg.CommitSig, + HtlcSigs: bobSigMsg.HtlcSigs, + }) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -3863,11 +3878,11 @@ func TestChanSyncFailure(t *testing.T) { t.Fatalf("unable to recv bob's htlc: %v", err) } - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("unable to sign next commit: %v", err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatalf("unable to receive commit sig: %v", err) } @@ -4085,7 +4100,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get her signature. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Restart both channels to simulate a connection restart. @@ -4144,19 +4159,20 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { t.Fatalf("expected a CommitSig message, instead have %v", spew.Sdump(aliceMsgsToSend[1])) } - if commitSigMsg.CommitSig != aliceSig { + if commitSigMsg.CommitSig != aliceNewCommit.CommitSig { t.Fatalf("commit sig msgs don't match: expected %x got %x", - aliceSig, commitSigMsg.CommitSig) + aliceNewCommit.CommitSig, commitSigMsg.CommitSig) } - if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) { + if len(commitSigMsg.HtlcSigs) != len(aliceNewCommit.HtlcSigs) { t.Fatalf("wrong number of htlc sigs: expected %v, got %v", - len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs)) + len(aliceNewCommit.HtlcSigs), + len(commitSigMsg.HtlcSigs)) } for i, htlcSig := range commitSigMsg.HtlcSigs { - if htlcSig != aliceHtlcSigs[i] { + if htlcSig != aliceNewCommit.HtlcSigs[i] { t.Fatalf("htlc sig msgs don't match: "+ "expected %x got %x", - aliceHtlcSigs[i], htlcSig) + aliceNewCommit.HtlcSigs[i], htlcSig) } } @@ -4166,15 +4182,15 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { t.Fatalf("unable to update fee for Bob's channel: %v", err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -4312,7 +4328,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get the signature. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommitSig, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Before restarting Alice, to mimic the old format, we fetch the @@ -4365,15 +4381,15 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // We send Alice's commitment signatures, and finish the state // transition. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommitSig.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommitSigs, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommitSigs.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -5010,7 +5026,7 @@ func TestSignCommitmentFailNotLockedIn(t *testing.T) { // If we now try to initiate a state update, then it should fail as // Alice is unable to actually create a new state. - _, _, _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment() if err != ErrNoWindow { t.Fatalf("expected ErrNoWindow, instead have: %v", err) } @@ -5049,11 +5065,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // We'll now manually initiate a state transition between Alice and // bob. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5078,12 +5094,12 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Now, have Bob initiate a transition to lock in the Adds sent by // Alice. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5123,11 +5139,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // We'll now initiate another state transition, but this time Bob will // lead. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5158,11 +5174,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Now, begin another state transition led by Alice, and fail the second // HTLC part-way through the dance. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5215,11 +5231,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Have Alice initiate a state transition, which does not include the // HTLCs just re-added to the channel state. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5244,12 +5260,12 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { } // Now initiate a final update from Bob to lock in the final Fail. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5276,11 +5292,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Finally, have Bob initiate a state transition that locks in the Fail // added after the restart. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5335,15 +5351,15 @@ func TestInvalidCommitSigError(t *testing.T) { } // Alice will now attempt to initiate a state transition. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign new commit") // Before the signature gets to Bob, we'll mutate it, such that the // signature is now actually invalid. - aliceSig[0] ^= 88 + aliceNewCommit.CommitSig[0] ^= 88 // Bob should reject this new state, and return the proper error. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err == nil { t.Fatalf("bob accepted invalid state but shouldn't have") } @@ -5537,7 +5553,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { // With the HTLC added, we'll now manually initiate a state transition // from Alice to Bob. - _, _, _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } @@ -5781,9 +5797,9 @@ func TestMaxAcceptedHTLCs(t *testing.T) { } // Add a commitment to Bob's commitment chain. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to recv new commitment") // The next HTLC should fail with ErrMaxHTLCNumber. The index is incremented @@ -5804,7 +5820,9 @@ func TestMaxAcceptedHTLCs(t *testing.T) { // fail) an HTLC from Alice when exchanging asynchronous payments. We want to // mimic the following case where Bob's commitment transaction is full before // starting: -// Alice Bob +// +// Alice Bob +// // 1. <---settle/fail--- // 2. <-------sig------- // 3. --------sig------> (covers an add sent before step 1) @@ -5885,18 +5903,18 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { t.Fatalf("unable to receive fail htlc: %v", err) } - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive new commitment") // Cover the HTLC referenced with id equal to numHTLCs-1 with a new // signature (step 3). - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive new commitment") // Both sides exchange revocations as in step 4 & 5. @@ -5923,10 +5941,10 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { // Receiving the commitment should succeed as in step 7 since space was // made. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive new commitment") } @@ -6601,11 +6619,11 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { } // Let Alice sign a new state, which will include the HTLC just sent. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Bob receives this commitment signature, and revokes his old state. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -6631,7 +6649,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // and remote commit chains are updated in an async fashion. Since the // remote chain was updated with the latest state (since Bob sent the // revocation earlier) we can keep advancing the remote commit chain. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // After Alice has signed this commitment, her local commitment will @@ -6776,9 +6794,9 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { restoreAndAssert(t, aliceChannel, 1, 0, 0, 0) // Bob sends a signature. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") // When Alice receives Bob's new commitment, the logs will stay the @@ -6803,9 +6821,9 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { // Now send a signature from Alice. This will give Bob a new commitment // where the HTLC is removed. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") // When sending a new commitment, Alice will add a pending commit to @@ -6871,7 +6889,7 @@ func TestDuplicateFailRejection(t *testing.T) { // We'll now have Bob sign a new commitment to lock in the HTLC fail // for Alice. - _, _, _, err = bobChannel.SignNextCommitment() + _, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") // We'll now force a restart for Bob and Alice, so we can test the @@ -6939,7 +6957,7 @@ func TestDuplicateSettleRejection(t *testing.T) { // We'll now have Bob sign a new commitment to lock in the HTLC fail // for Alice. - _, _, _, err = bobChannel.SignNextCommitment() + _, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") // We'll now force a restart for Bob and Alice, so we can test the @@ -7024,7 +7042,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { } // Let Alice sign a new state, which will include the HTLC just sent. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // The HTLC should only be on the pending remote commitment, so the @@ -7034,7 +7052,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { ) // Bob receives this commitment signature, and revokes his old state. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7056,7 +7074,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Now let Bob send the commitment signature making the HTLC lock in on // Alice's commitment. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // At this stage Bob has a pending remote commitment. Make sure @@ -7064,7 +7082,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // heights. bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 1, 1) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7094,7 +7112,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Send a new signature from Alice to Bob, making Alice have a pending // remote commitment. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // A restoration should keep the add heights iof the first HTLC, and @@ -7106,7 +7124,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { t, aliceChannel, false, 1, 0, 2, ) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7133,7 +7151,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Sign a new state for Alice, making Bob have a pending remote // commitment. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // The signing of a new commitment for Alice should have given the new @@ -7142,7 +7160,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 1, 2, 2) // Alice should receive the commitment and send over a revocation. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7170,7 +7188,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { require.NoError(t, err, "unable to recv htlc cancel") // Now Bob signs for the fail update. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Bob has a pending commitment for Alice, it shouldn't affect the add @@ -7179,7 +7197,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { _ = restoreAndAssertCommitHeights(t, bobChannel, true, 1, 2, 2) // Alice receives commitment, sends revocation. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7233,15 +7251,15 @@ func TestForceCloseBorkedState(t *testing.T) { // Do the commitment dance until Bob sends a revocation so Alice is // able to receive the revocation, and then also make a new state // herself. - aliceSigs, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") - err = bobChannel.ReceiveNewCommitment(aliceSigs, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") revokeMsg, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSigs, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") - err = aliceChannel.ReceiveNewCommitment(bobSigs, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") // Now that we have a new Alice channel, we'll force close once to @@ -7273,7 +7291,7 @@ func TestForceCloseBorkedState(t *testing.T) { // We manually advance the commitment tail here since the above // ReceiveRevocation call will fail before it's actually advanced. aliceChannel.remoteCommitChain.advanceTail() - _, _, _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment() if err != channeldb.ErrChanBorked { t.Fatalf("sign commitment should have failed: %v", err) } @@ -7547,11 +7565,11 @@ func TestChannelFeeRateFloor(t *testing.T) { } // Check that alice can still sign commitments. - sig, htlcSigs, _, err := alice.SignNextCommitment() + aliceNewCommit, err := alice.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Check that bob can still receive commitments. - err = bob.ReceiveNewCommitment(sig, htlcSigs) + err = bob.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatalf("bob unable to process alice's new commitment: %v", err) @@ -8799,20 +8817,21 @@ func TestProcessAddRemoveEntry(t *testing.T) { // The full state transition of this test is: // // Alice Bob -// -----add-----> -// -----sig-----> -// <----rev------ -// <----sig------ -// -----rev-----> -// <----fail----- -// <----sig------ -// -----rev-----> -// -----sig-----X (does not reach Bob! Alice dies!) // -// -----sig-----> -// <----rev------ -// <----add------ -// <----sig------ +// -----add-----> +// -----sig-----> +// <----rev------ +// <----sig------ +// -----rev-----> +// <----fail----- +// <----sig------ +// -----rev-----> +// -----sig-----X (does not reach Bob! Alice dies!) +// +// -----sig-----> +// <----rev------ +// <----add------ +// <----sig------ // // The last sig was rejected with the old behavior of deleting unsigned // acked updates from the database after signing for them. The current @@ -8854,9 +8873,9 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob should send a commitment signature to Alice. // <----sig------ - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) // Alice should reply with a revocation. @@ -8869,7 +8888,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Alice should sign the next commitment and go down before // sending it. // -----sig-----X - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) newAliceChannel, err := NewLightningChannel( @@ -8880,7 +8899,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob receives Alice's signature. // -----sig-----> - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // Bob revokes his current commitment and sends a revocation @@ -8903,9 +8922,9 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob sends the final signature to Alice and Alice should not // reject it, given that we properly restore the unsigned acked // updates and therefore our update log is structured correctly. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) - err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -8915,16 +8934,17 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // The full state transition is: // // Alice Bob -// <----add----- -// <----sig----- -// -----rev----> -// -----sig----> -// <----rev----- -// ----fail----> -// -----sig----> -// <----rev----- -// *reconnect* -// <----sig----- +// +// <----add----- +// <----sig----- +// -----rev----> +// -----sig----> +// <----rev----- +// ----fail----> +// -----sig----> +// <----rev----- +// *reconnect* +// <----sig----- // // Alice should reject the last signature since the settle is not restored // into the local update log and thus calculates Bob's signature as invalid. @@ -8964,9 +8984,9 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Alice should send a commitment signature to Bob. // -----sig----> - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // Bob should reply with a revocation and Alice should save the fail as @@ -8988,9 +9008,9 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Bob sends the final signature and Alice should not reject it. // <----sig----- - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -9000,22 +9020,22 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // The full state transition of this test is: // // Alice Bob -// <----add------- -// <----sig------- -// -----rev------> -// -----sig------> -// <----rev------- -// ----settle----> -// -----sig------> -// <----rev------- -// <----sig------- -// -----add------> -// -----sig------> -// <----rev------- -// *restarts* -// -----rev------> -// <----sig------- // +// <----add------- +// <----sig------- +// -----rev------> +// -----sig------> +// <----rev------- +// ----settle----> +// -----sig------> +// <----rev------- +// <----sig------- +// -----add------> +// -----sig------> +// <----rev------- +// *restarts* +// -----rev------> +// <----sig------- func TestChannelSignedAckRegression(t *testing.T) { t.Parallel() @@ -9051,9 +9071,9 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // -----sig----> - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // <----rev----- @@ -9063,9 +9083,9 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // <----sig----- - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) // Create an HTLC that Alice will send to Bob. @@ -9078,9 +9098,9 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // -----sig----> - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // <----rev----- @@ -9108,11 +9128,11 @@ func TestChannelSignedAckRegression(t *testing.T) { // Bob should no longer fail to sign this commitment due to faulty // update logs. // <----sig----- - bobSig, bobHtlcSigs, _, err = newBobChannel.SignNextCommitment() + bobNewCommit, err = newBobChannel.SignNextCommitment() require.NoError(t, err) // Alice should receive the new commitment without hiccups. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -9184,9 +9204,9 @@ func TestIsChannelClean(t *testing.T) { // removed from both commitments. // ---sig---> - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9198,9 +9218,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9219,9 +9239,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9233,9 +9253,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---sig---> - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9257,9 +9277,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---sig---> - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9271,9 +9291,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9409,9 +9429,9 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { checkDust(bobChannel, htlc2Amt, htlc2Amt) // Alice signs for this HTLC and neither perspective should change. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) checkDust(aliceChannel, htlc2Amt, htlc1Amt+htlc2Amt) checkDust(bobChannel, htlc2Amt, htlc2Amt) @@ -9428,9 +9448,9 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { // The rest of the dance is completed and neither perspective should // change. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index 6020de53dbc..c1a9b5ccfdc 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -502,11 +502,11 @@ func calcStaticFee(chanType channeldb.ChannelType, numHTLCs int) btcutil.Amount // pending updates. This method is useful when testing interactions between two // live state machines. func ForceStateTransition(chanA, chanB *LightningChannel) error { - aliceSig, aliceHtlcSigs, _, aliceNonce, err := chanA.SignNextCommitment() + aliceNewCommit, err := chanA.SignNextCommitment() if err != nil { return err } - err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs, *aliceNonce) + err = chanB.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { return err } @@ -515,7 +515,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if err != nil { return err } - bobSig, bobHtlcSigs, _, bobNonce, err := chanB.SignNextCommitment() + bobNewCommit, err := chanB.SignNextCommitment() if err != nil { return err } @@ -523,7 +523,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if _, _, _, _, err := chanA.ReceiveRevocation(bobRevocation); err != nil { return err } - if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs, *bobNonce); err != nil { + if err := chanA.ReceiveNewCommitment(bobNewCommit.CommitSigs); err != nil { return err } diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index ab61cf34d11..d95486c7648 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -287,10 +287,10 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { // Execute commit dance to arrive at the point where the local node has // received the test commitment and the remote signature. - localSig, localHtlcSigs, _, err := localChannel.SignNextCommitment() + localNewCommit, err := localChannel.SignNextCommitment() require.NoError(t, err, "local unable to sign commitment") - err = remoteChannel.ReceiveNewCommitment(localSig, localHtlcSigs) + err = remoteChannel.ReceiveNewCommitment(localNewCommit.CommitSigs) require.NoError(t, err) revMsg, _, err := remoteChannel.RevokeCurrentCommitment() @@ -299,16 +299,16 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { _, _, _, _, err = localChannel.ReceiveRevocation(revMsg) require.NoError(t, err) - remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment() + remoteNewCommit, err := remoteChannel.SignNextCommitment() require.NoError(t, err) - require.Equal(t, test.RemoteSigHex, hex.EncodeToString(remoteSig.ToSignatureBytes())) + require.Equal(t, test.RemoteSigHex, hex.EncodeToString(remoteNewCommit.CommitSig.ToSignatureBytes())) - for i, sig := range remoteHtlcSigs { + for i, sig := range remoteNewCommit.HtlcSigs { require.Equal(t, test.HtlcDescs[i].RemoteSigHex, hex.EncodeToString(sig.ToSignatureBytes())) } - err = localChannel.ReceiveNewCommitment(remoteSig, remoteHtlcSigs) + err = localChannel.ReceiveNewCommitment(remoteNewCommit.CommitSigs) require.NoError(t, err) _, _, err = localChannel.RevokeCurrentCommitment() @@ -711,10 +711,10 @@ func testSpendValidation(t *testing.T, tweakless bool) { // the commitment transaction. // // The following spending cases are covered by this test: -// * Alice's spend from the delayed output on her commitment transaction. -// * Bob's spend from Alice's delayed output when she broadcasts a revoked +// - Alice's spend from the delayed output on her commitment transaction. +// - Bob's spend from Alice's delayed output when she broadcasts a revoked // commitment transaction. -// * Bob's spend from his unencumbered output within Alice's commitment +// - Bob's spend from his unencumbered output within Alice's commitment // transaction. func TestCommitmentSpendValidation(t *testing.T) { t.Parallel() From ffe0b5cacd9895b8da7752ebe654a635583f3516 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:20:21 -0800 Subject: [PATCH 43/87] input: add tapleaf+control block logic for sender HTLC scripts --- .gitignore | 2 + input/script_utils.go | 249 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) diff --git a/.gitignore b/.gitignore index 157010fd932..8303df3003d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,5 @@ coverage.txt # Release build directory (to avoid build.vcs.modified Golang build tag to be # set to true by having untracked files in the working directory). /lnd-*/ + +test_lnd* diff --git a/input/script_utils.go b/input/script_utils.go index d09ba541066..c0c632279db 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -480,6 +480,255 @@ func SenderHtlcSpendTimeout(receiverSig Signature, return witnessStack, nil } +// SenderHTLCTapLeafTimeout returns the full tapscript leaf for the timeout +// path of the sender HTLC. This is a small script that allows the sender to +// timeout the HTLC after a period of time: +// +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +func SenderHTLCTapLeafTimeout(senderHtlcKey, + receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + timeoutLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(timeoutLeafScript), nil +} + +// SenderHTLCTapLeafSuccess returns the full tapscript leaf for the success +// path of the sender HTLC. This is a small script that allows the sender to +// redeem the HTLC with a pre-image: +// +// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 +// OP_EQUALVERIFY +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, + paymentHash []byte) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Check that the specified pre-images matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the remote party's signature, then make them wait 1 block + // after confirmation to properly sweep. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + successLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(successLeafScript), nil +} + +// HtlcScriptTree... +type HtlcScriptTree struct { + // TaprootKey... + TaprootKey *btcec.PublicKey + + // SuccessTapLeaf... + SuccessTapLeaf txscript.TapLeaf + + // TimeoutTapLeaf... + TimeoutTapLeaf txscript.TapLeaf + + // TapscriptTree... + TapscriptTree *txscript.IndexedTapScriptTree +} + +// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor +// the HTLC key for HTLCs on the sender's commitment. +func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + + // First, we'll obtain the tap leaves for both the success and timeout + // path. + successTapLeaf, err := SenderHTLCTapLeafSuccess( + receiverHtlcKey, payHash, + ) + if err != nil { + return nil, err + } + timeoutTapLeaf, err := SenderHTLCTapLeafTimeout( + senderHtlcKey, receiverHtlcKey, + ) + if err != nil { + return nil, err + } + + // With the two leaves obtained, we'll now make the tapscript tree, + // then obtain the root from that + tapscriptTree := txscript.AssembleTaprootScriptTree( + successTapLeaf, timeoutTapLeaf, + ) + + tapScriptRoot := tapscriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that HTLCs will be sent to. + htlcKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return &HtlcScriptTree{ + TaprootKey: htlcKey, + SuccessTapLeaf: successTapLeaf, + TimeoutTapLeaf: timeoutTapLeaf, + TapscriptTree: tapscriptTree, + }, nil +} + +// SenderHTLCScriptTaproot constructs the taproot witness program (schnorr key) +// for an outgoing HTLC on the sender's version of the commitment transaction. +// This method returns the top level tweaked public key that commits to both +// the script paths. +// +// The returned key commits to a tapscript tree with two possible paths: +// +// - Timeout path: +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +// +// - Success path: +// OP_SIZE 32 OP_EQUALVERIFY +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// +// The timeout path can be spent with a witness of (sender timeout): +// +// +// +// The success path can be spent with a valid control block, and a witness of +// (receiver redeem): +// +// +// +// The top level keyspend key is the revocation key, which allows a defender to +// unilaterally spend the created output. +func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + + // Given all the necessary parameters, we'll return the HTLC script + // tree that includes the top level output script, as well as the two + // tap leaf paths. + return senderHtlcTapScriptTree( + senderHtlcKey, receiverHtlcKey, revokeKey, payHash, + ) +} + +// SenderHTLCScriptTaprootRedeem creates a valid witness needed to redeem a +// sender taproot HTLC with the pre-image. The returned witness is valid and +// includes the control block required to spend the output. +func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx, preimage []byte, revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // In addition to the signature and the witness/leaf script, we also + // need to make a control block proof using the tapscript tree. + successTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] + successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] + successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + + // The final witness stack is: + // + witnessStack := make(wire.TxWitness, 4) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[1] = preimage + witnessStack[2] = signDesc.WitnessScript + witnessStack[4], err = successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// SenderHTLCScriptTaprootTimeout creates a valid witness needed to timeout an +// HTLC on the sender's commitment transaction. The returned witness is valid and +// includes the control block required to spend the output. +func SenderHTLCScriptTaprootTimeout(receiverSig Signature, + receiverSigHash txscript.SigHashType, signer Signer, + signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx, + revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(htlcTimeoutTx, signDesc) + if err != nil { + return nil, err + } + + // With the sweep signature obtained, we'll obtain the control block + // proof needed to perform a valid spend for the timeout path. + timeoutTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] + timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] + timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + + // The final witness stack is: + // + witnessStack := make(wire.TxWitness, 4) + witnessStack[0] = append(receiverSig.Serialize(), byte(receiverSigHash)) + witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[2] = signDesc.WitnessScript + witnessStack[3], err = timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// SenderHTLCScriptTaprootRevoke creates a valid witness needed to spend the +// revocation path of the HTLC. This uses a plain keyspend using the specified +// revocation key. +func SenderHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + + return witnessStack, nil +} + // ReceiverHTLCScript constructs the public key script for an incoming HTLC // output payment for the receiver's version of the commitment transaction. The // possible execution paths from this script include: From b285a5866cb4ff7652484da3dcf89857a9e64034 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:20:41 -0800 Subject: [PATCH 44/87] input: add tapleaf+ctrlblock logic for receiver HTLC scripts --- input/script_utils.go | 246 ++++++++++++++++++++++++ lnwallet/btcwallet/musig.go | 1 + lnwallet/btcwallet/test_utils.go | 1 + lnwallet/musession/manager.go | 309 +++++++++++++++++++++++++++++++ 4 files changed, 557 insertions(+) create mode 100644 lnwallet/btcwallet/musig.go create mode 100644 lnwallet/btcwallet/test_utils.go create mode 100644 lnwallet/musession/manager.go diff --git a/input/script_utils.go b/input/script_utils.go index c0c632279db..fd058b0050b 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -1002,6 +1002,252 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor, return witnessStack, nil } +// ReceiverHtlcTapLeafTimeout returns the full tapscript leaf for the timeout +// path of the sender HTLC. This is a small script that allows the sender +// timeout the HTLC after expiry: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +func ReceiverHtlcTapLeafTimeout(receiverHtlcKey *btcec.PublicKey, + cltvExpiry uint32) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // The first part of the script will verify a signature from the + // receiver authorizing the spend. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + // The second portion will ensure that the CLTV expiry on the spending + // transaction is correct. + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + timeoutLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, nil + } + + return txscript.NewBaseTapLeaf(timeoutLeafScript), nil +} + +// ReceiverHtlcTapLeafSuccess returns the full tapscript leaf for the success +// path for an HTLC on the receiver's commitment transaction. This script +// allows the receiver to redeem an HTLC with knowledge of the preimage: +// +// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 +// OP_EQUALVERIFY +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, + senderHtlcKey *btcec.PublicKey, + paymentHash []byte) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Check that the specified pre-images matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the "2-of-2" multi-sig that requires both parties to sign + // off. + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + successLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(successLeafScript), nil +} + +// receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor +// the HTLC key for HTLCs on the receiver's commitment. +func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte, + cltvExpiry uint32) (*HtlcScriptTree, error) { + + // First, we'll obtain the tap leaves for both the success and timeout + // path. + successTapLeaf, err := ReceiverHtlcTapLeafSuccess( + receiverHtlcKey, senderHtlcKey, payHash, + ) + if err != nil { + return nil, err + } + timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout( + receiverHtlcKey, cltvExpiry, + ) + if err != nil { + return nil, err + } + + // With the two leaves obtained, we'll now make the tapscript tree, + // then obtain the root from that + tapscriptTree := txscript.AssembleTaprootScriptTree( + successTapLeaf, timeoutTapLeaf, + ) + + tapScriptRoot := tapscriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that HTLCs will be sent to. + htlcKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return &HtlcScriptTree{ + TaprootKey: htlcKey, + SuccessTapLeaf: successTapLeaf, + TimeoutTapLeaf: timeoutTapLeaf, + TapscriptTree: tapscriptTree, + }, nil +} + +// ReceiverHTLCScriptTaproot cosntructs the taproot witness program (schnor +// key) for an outgoing HTLC on the receiver's version of the commitment +// transaction. This method returns the top level tweaked public key that +// commits to both the script paths. +// +// The returned key commits to a tapscript tree with two possible paths: +// +// - The timeout path: +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// +// - Success path: +// OP_SIZE 32 OP_EQUALVERIFY +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +// +// The timeout path can be be spent with a witness of: +// - +// +// The success path can be spent with a witness of: +// - +// +// The top level keyspend key is the revocation key, which allows a defender to +// unilaterally spend the created output. Both the final output key as well as +// the tap leaf are returned. +func ReceiverHTLCScriptTaproot(cltvExpiry uint32, + senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, + payHash []byte) (*HtlcScriptTree, error) { + + // Given all the necessary parameters, we'll return the HTLC script + // tree that includes the top level output script, as well as the two + // tap leaf paths. + return receiverHtlcTapScriptTree( + senderHtlcKey, receiverHtlcKey, revocationKey, payHash, + cltvExpiry, + ) +} + +// ReceiverHTLCScriptTaprootRedeem creates a valid witness needed to redeem a +// receiver taproot HTLC with the pre-image. The returned witness is valid and +// includes the control block required to spend the output. +func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, + senderSigHash txscript.SigHashType, paymentPreimage []byte, + signer Signer, signDesc *SignDescriptor, + htlcSuccessTx *wire.MsgTx, revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // First, we'll generate a signature for the HTLC success transaction. + // The signDesc should be signing with the public key used as the + // receiver's public key and also the correct single tweak. + sweepSig, err := signer.SignOutputRaw(htlcSuccessTx, signDesc) + if err != nil { + return nil, err + } + + // In addition to the signature and the witness/leaf script, we also + // need to make a control block proof using the tapscript tree. + timeoutTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] + timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] + timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + + // The final witness stack is: + // * + witnessStack := wire.TxWitness(make([][]byte, 5)) + witnessStack[0] = append(senderSig.Serialize(), byte(senderSigHash)) + witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[2] = paymentPreimage + witnessStack[3] = signDesc.WitnessScript + witnessStack[4], err = timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// ReceiverHtlcTapLeafTimeout creates a valid witness needed to timeout an HTLC +// on the receiver's commitment transaction after the timeout has elapsed +func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx, cltvExpiry int32, revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // If the caller set a proper timeout value, then we'll apply it + // directly to the transaction. + // + // TODO(roasbeef): helper func + if cltvExpiry != -1 { + // The HTLC output has an absolute time period before we are + // permitted to recover the pending funds. Therefore we need to + // set the locktime on this sweeping transaction in order to + // pass Script verification. + sweepTx.LockTime = uint32(cltvExpiry) + } + + // With the lock time on the transaction set, we'll not generate a + // signature for the sweep transaction. The passed sign descriptor + // should be created using the raw public key of the sender (w/o the + // single tweak applied), and the single tweak set to the proper value + // taking into account the current state's point. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // In addition to the signature and the witness/leaf script, we also + // need to make a control block proof using the tapscript tree. + successTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] + successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] + successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + + // The final witness is pretty simple, we just need to present a valid + // signature for the script, and then provide the control block. + witnessStack := make(wire.TxWitness, 3) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[1] = signDesc.WitnessScript + witnessStack[2], err = successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + // SecondLevelHtlcScript is the uniform script that's used as the output for // the second-level HTLC transactions. The second level transaction act as a // sort of covenant, ensuring that a 2-of-2 multi-sig output can only be diff --git a/lnwallet/btcwallet/musig.go b/lnwallet/btcwallet/musig.go new file mode 100644 index 00000000000..951ade5b786 --- /dev/null +++ b/lnwallet/btcwallet/musig.go @@ -0,0 +1 @@ +package btcwallet diff --git a/lnwallet/btcwallet/test_utils.go b/lnwallet/btcwallet/test_utils.go new file mode 100644 index 00000000000..951ade5b786 --- /dev/null +++ b/lnwallet/btcwallet/test_utils.go @@ -0,0 +1 @@ +package btcwallet diff --git a/lnwallet/musession/manager.go b/lnwallet/musession/manager.go new file mode 100644 index 00000000000..086e3765d30 --- /dev/null +++ b/lnwallet/musession/manager.go @@ -0,0 +1,309 @@ +package musession + +import ( + "crypto/sha256" + "fmt" + "sync" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" +) + +// MuSig2State is a struct that holds on to the internal signing session state +// of a MuSig2 session. +type MuSig2State struct { + // MuSig2SessionInfo is the associated meta information of the signing + // session. + input.MuSig2SessionInfo + + // context is the signing context responsible for keeping track of the + // public keys involved in the signing process. + context *musig2.Context + + // session is the signing session responsible for keeping track of the + // nonces and partial signatures involved in the signing process. + session *musig2.Session +} + +// PrivKeyFetcher... +type PrivKeyFetcher func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) + +// Manager... +type Manager struct { + sync.Mutex + + keyFetcher PrivKeyFetcher + + musig2Sessions map[input.MuSig2SessionID]*MuSig2State +} + +// NewManager... +func NewManager(keyFetcher PrivKeyFetcher) *Manager { + return &Manager{ + keyFetcher: keyFetcher, + musig2Sessions: make(map[input.MuSig2SessionID]*MuSig2State), + } +} + +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +// +// The set of sessionOpts are _optional_ and allow a caller to modify the +// generated sessions. As an example the local nonce might already be generated +// ahead of time. +func (m *Manager) MuSig2CreateSession(keyLoc keychain.KeyLocator, + allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, + otherSignerNonces [][musig2.PubNonceSize]byte, + sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { + + // We need to derive the private key for signing. In the remote signing + // setup, this whole RPC call will be forwarded to the signing + // instance, which requires it to be stateful. + privKey, err := m.keyFetcher(&keychain.KeyDescriptor{ + KeyLocator: keyLoc, + }) + if err != nil { + return nil, fmt.Errorf("error deriving private key: %v", err) + } + + // The context keeps track of all signing keys and our local key. + allOpts := append( + []musig2.ContextOption{ + musig2.WithKnownSigners(allSignerPubKeys), + }, + tweaks.ToContextOptions()..., + ) + musigContext, err := musig2.NewContext(privKey, true, allOpts...) + if err != nil { + return nil, fmt.Errorf("error creating MuSig2 signing "+ + "context: %v", err) + } + + // The session keeps track of the own and other nonces. + musigSession, err := musigContext.NewSession(sessionOpts...) + if err != nil { + return nil, fmt.Errorf("error creating MuSig2 signing "+ + "session: %v", err) + } + + // TODO(roasbeef): actually want to expose the context for channels so + // don't always need to re-create? + + // Add all nonces we might've learned so far. + haveAllNonces := false + for _, otherSignerNonce := range otherSignerNonces { + haveAllNonces, err = musigSession.RegisterPubNonce( + otherSignerNonce, + ) + if err != nil { + return nil, fmt.Errorf("error registering other "+ + "signer public nonce: %v", err) + } + } + + // Register the new session. + combinedKey, err := musigContext.CombinedKey() + if err != nil { + return nil, fmt.Errorf("error getting combined key: %v", err) + } + session := &MuSig2State{ + MuSig2SessionInfo: input.MuSig2SessionInfo{ + SessionID: input.NewMuSig2SessionID( + combinedKey, musigSession.PublicNonce(), + ), + PublicNonce: musigSession.PublicNonce(), + CombinedKey: combinedKey, + TaprootTweak: tweaks.HasTaprootTweak(), + HaveAllNonces: haveAllNonces, + }, + context: musigContext, + session: musigSession, + } + + // The internal key is only calculated if we are using a taproot tweak + // and need to know it for a potential script spend. + if tweaks.HasTaprootTweak() { + internalKey, err := musigContext.TaprootInternalKey() + if err != nil { + return nil, fmt.Errorf("error getting internal key: %v", + err) + } + session.TaprootInternalKey = internalKey + } + + // Since we generate new nonces for every session, there is no way that + // a session with the same ID already exists. So even if we call the API + // twice with the same signers, we still get a new ID. + m.Lock() + m.musig2Sessions[session.SessionID] = session + m.Unlock() + + return &session.MuSig2SessionInfo, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (m *Manager) MuSig2Sign(sessionID input.MuSig2SessionID, + msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + session, ok := m.musig2Sessions[sessionID] + if !ok { + return nil, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // We can only sign once we have all other signer's nonces. + if !session.HaveAllNonces { + return nil, fmt.Errorf("only have %d of %d required nonces", + session.session.NumRegisteredNonces(), + len(session.context.SigningKeys())) + } + + // Create our own partial signature with the local signing key. + partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys()) + if err != nil { + return nil, fmt.Errorf("error signing with local key: %v", err) + } + + // Clean up our local state if requested. + if cleanUp { + delete(m.musig2Sessions, sessionID) + } + + return partialSig, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (m *Manager) MuSig2CombineSig(sessionID input.MuSig2SessionID, + partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool, + error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + session, ok := m.musig2Sessions[sessionID] + if !ok { + return nil, false, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // Make sure we don't exceed the number of expected partial signatures + // as that would indicate something is wrong with the signing setup. + if session.HaveAllSigs { + return nil, true, fmt.Errorf("already have all partial" + + "signatures") + } + + // Add all sigs we got so far. + var ( + finalSig *schnorr.Signature + err error + ) + for _, otherPartialSig := range partialSigs { + session.HaveAllSigs, err = session.session.CombineSig( + otherPartialSig, + ) + if err != nil { + return nil, false, fmt.Errorf("error combining "+ + "partial signature: %v", err) + } + } + + // If we have all partial signatures, we should be able to get the + // complete signature now. We also remove this session from memory since + // there is nothing more left to do. + if session.HaveAllSigs { + finalSig = session.session.FinalSig() + delete(m.musig2Sessions, sessionID) + } + + return finalSig, session.HaveAllSigs, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (m *Manager) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + _, ok := m.musig2Sessions[sessionID] + if !ok { + return fmt.Errorf("session with ID %x not found", sessionID[:]) + } + + delete(m.musig2Sessions, sessionID) + + return nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (m *Manager) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, + otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + session, ok := m.musig2Sessions[sessionID] + if !ok { + return false, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // Make sure we don't exceed the number of expected nonces as that would + // indicate something is wrong with the signing setup. + if session.HaveAllNonces { + return true, fmt.Errorf("already have all nonces") + } + + numSigners := len(session.context.SigningKeys()) + remainingNonces := numSigners - session.session.NumRegisteredNonces() + if len(otherSignerNonces) > remainingNonces { + return false, fmt.Errorf("only %d other nonces remaining but "+ + "trying to register %d more", remainingNonces, + len(otherSignerNonces)) + } + + // Add all nonces we've learned so far. + var err error + for _, otherSignerNonce := range otherSignerNonces { + session.HaveAllNonces, err = session.session.RegisterPubNonce( + otherSignerNonce, + ) + if err != nil { + return false, fmt.Errorf("error registering other "+ + "signer public nonce: %v", err) + } + } + + return session.HaveAllNonces, nil +} From 6b22b0f6340f635c1fb4c13626e6eb29d3231a39 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:21:37 -0800 Subject: [PATCH 45/87] input: add new taproot second level HTLC scripts+txns --- input/script_utils.go | 157 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/input/script_utils.go b/input/script_utils.go index fd058b0050b..329d49dac66 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -1321,6 +1321,163 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, return builder.Script() } +// TODO(roasbeef): move all taproot stuff to new file? + +// TaprootSecondLevelTapLeaf constructs the tap leaf used as the sole script +// path for a second level HTLC spend. +// +// The final script used is: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, + csvDelay uint32) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Ensure the proper party can sign for this output. + builder.AddData(schnorr.SerializePubKey(delayKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + // Assuming the above passes, then we'll now ensure that the CSV delay + // has been upheld, dropping the int we pushed on. If the sig above is + // valid, then a 1 will be left on the stack. + builder.AddInt64(int64(csvDelay)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + secondLevelLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(secondLevelLeafScript), nil +} + +// SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to +// generate the taptweak to create the final output and also control block. +func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, + csvDelay uint32) (*txscript.IndexedTapScriptTree, error) { + + // First grab the second level leaf script we need to create the top level + // output. + secondLevelTapLeaf, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay) + if err != nil { + return nil, err + } + + // Now that we have the sole second level script, we can create the + // tapscript tree that commits to both the leaves. + return txscript.AssembleTaprootScriptTree(secondLevelTapLeaf), nil +} + +// TaprootSecondLevelHtlcScript is the uniform script that's used as the output +// for the second-level HTLC transaction. The second level transaction acts as +// an off-chain 2-of-2 covenant that can only be spent a particular way and to +// a particular output. +// +// Possible Input Scripts: +// - Claiming the HTLC output with a pre-image or a timeout: +// - +// +// The script main script lets the broadcaster spend after a delay the script +// path: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// The keyspend path require knowledge of the top level revocation private key. +func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey, + csvDelay uint32) (*btcec.PublicKey, error) { + + // First, we'll make the tapscript tree that commits to the redemption + // path. + tapScriptTree, err := SecondLevelHtlcTapscriptTree( + delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that the second level spend will + // create. + redemptionKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return redemptionKey, nil +} + +// TaprootHtlcSpendRevoke spends a second-level HTLC output via the revocation +// path. This uses the top level keyspend path to redeem the contested output. +// +// The passed SignDescriptor MUST have the proper witness script and also the +// proper top-level tweak derived from the tapscript tree for the second level +// output. +func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, + revokeTx *wire.MsgTx) (wire.TxWitness, error) { + + // We don't need any spacial modifications to the transaction as this + // is just sweeping a revoked HTLC output. So we'll generate a regular + // schnorr signature. + sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + + return witnessStack, nil +} + +// TaprootHtlcSpendSuccess spends a second-level HTLC output via the redemption +// path. This should be used to sweep funds after the pre-image is known or the +// timeout has elapsed on the commitment transaction of the broadcaster. +// +// NOTE: The caller MUST set the txn version, sequence number, and sign +// descriptor's sig hash cache before invocation. +func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, + revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // First, we'll generate the sweep signature based on the populated + // sign desc. This should give us a valid schnorr signature for the + // sole script path leaf. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // Now that we have the sweep signature, we'll construct the control + // block needed to spend the script path. + redeemTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + redeemIdx := tapscriptTree.LeafProofIndex[redeemTapLeafHash] + redeemMerkleProof := tapscriptTree.LeafMerkleProofs[redeemIdx] + redeemControlBlock := redeemMerkleProof.ToControlBlock(revokeKey) + + // Now that we have the redeem control block, we can construct the + // final witness needed to spend the script: + // + // + witnessStack := make(wire.TxWitness, 3) + witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[2] = signDesc.WitnessScript + witnessStack[3], err = redeemControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + // LeaseSecondLevelHtlcScript is the uniform script that's used as the output for // the second-level HTLC transactions. The second level transaction acts as a // sort of covenant, ensuring that a 2-of-2 multi-sig output can only be From c884b4e2c33b46ca7a9be04763095206dfbc184b Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:21:52 -0800 Subject: [PATCH 46/87] build: update to musig2 1.0 --- go.mod | 8 ++++---- go.sum | 13 ++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 79c7fdd46c7..6054c5f2bcc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 github.com/btcsuite/btcd v0.23.1 - github.com/btcsuite/btcd/btcec/v2 v2.2.1 + github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcutil v1.1.2 github.com/btcsuite/btcd/btcutil/psbt v1.1.5 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -44,7 +44,7 @@ require ( github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 github.com/miekg/dns v1.1.43 github.com/prometheus/client_golang v1.11.0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.0 github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 github.com/urfave/cli v1.22.9 go.etcd.io/etcd/client/pkg/v3 v3.5.0 @@ -119,7 +119,7 @@ require ( github.com/sirupsen/logrus v1.7.0 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/ulikunitz/xz v0.5.10 // indirect @@ -153,7 +153,7 @@ require ( gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index ab09cd58a9f..135a3ff6452 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZg github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.2.1 h1:xP60mv8fvp+0khmrN0zTdPC3cNm24rfeE6lh2R/Yv3E= -github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= @@ -634,16 +634,18 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -1124,8 +1126,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From a7dca6bf261588b86ca0d70f865e0177855266cf Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:26:47 -0800 Subject: [PATCH 47/87] lnwallet+input: move mock signer into lnwallet pkg, use new musig session In this commit, we move the mock signer into the wallet pkg to avoid a circular dependnacy. We also use the newly extracted musig session manager in order to allow the mocks to utilize the standardized musig2 algos. --- input/test_utils.go | 245 ---------------------------- lntest/mock/signer.go | 2 +- lnwallet/btcwallet/btcwallet.go | 30 ++-- lnwallet/btcwallet/signer.go | 276 -------------------------------- lnwallet/test_utils.go | 240 ++++++++++++++++++++++++++- lnwallet/transactions_test.go | 8 +- 6 files changed, 260 insertions(+), 541 deletions(-) diff --git a/input/test_utils.go b/input/test_utils.go index 015bfd9530d..de1ab6f1c81 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -1,246 +1 @@ package input - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/keychain" -) - -var ( - - // For simplicity a single priv key controls all of our test outputs. - testWalletPrivKey = []byte{ - 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, - 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, - 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, - 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, - } - - // We're alice :) - bobsPrivKey = []byte{ - 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, - 0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, - 0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, - 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, - } - - // Use a hard-coded HD seed. - testHdSeed = chainhash.Hash{ - 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, - 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, - 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, - 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, - } -) - -// MockSigner is a simple implementation of the Signer interface. Each one has -// a set of private keys in a slice and can sign messages using the appropriate -// one. -type MockSigner struct { - Privkeys []*btcec.PrivateKey - NetParams *chaincfg.Params -} - -// SignOutputRaw generates a signature for the passed transaction according to -// the data within the passed SignDescriptor. -func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *SignDescriptor) (Signature, error) { - - pubkey := signDesc.KeyDesc.PubKey - switch { - case signDesc.SingleTweak != nil: - pubkey = TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak) - case signDesc.DoubleTweak != nil: - pubkey = DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) - } - - hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) - privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("mock signer does not have key") - } - - sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript, - signDesc.HashType, privKey) - if err != nil { - return nil, err - } - - return ecdsa.ParseDERSignature(sig[:len(sig)-1]) -} - -// ComputeInputScript generates a complete InputIndex for the passed transaction -// with the signature as defined within the passed SignDescriptor. This method -// should be capable of generating the proper input script for both regular -// p2wkh output and p2wkh outputs nested within a regular p2sh output. -func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, error) { - scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs( - signDesc.Output.PkScript, m.NetParams) - if err != nil { - return nil, err - } - - switch scriptType { - case txscript.PubKeyHashTy: - privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, - signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("mock signer does not have key for "+ - "address %v", addresses[0]) - } - - sigScript, err := txscript.SignatureScript( - tx, signDesc.InputIndex, signDesc.Output.PkScript, - txscript.SigHashAll, privKey, true, - ) - if err != nil { - return nil, err - } - - return &Script{SigScript: sigScript}, nil - - case txscript.WitnessV0PubKeyHashTy: - privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, - signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("mock signer does not have key for "+ - "address %v", addresses[0]) - } - - witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, - signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) - if err != nil { - return nil, err - } - - return &Script{Witness: witnessScript}, nil - - default: - return nil, fmt.Errorf("unexpected script type: %v", scriptType) - } -} - -// MuSig2CreateSession creates a new MuSig2 signing session using the local -// key identified by the key locator. The complete list of all public keys of -// all signing parties must be provided, including the public key of the local -// signing key. If nonces of other parties are already known, they can be -// submitted as well to reduce the number of method calls necessary later on. -func (m *MockSigner) MuSig2CreateSession(keychain.KeyLocator, - []*btcec.PublicKey, *MuSig2Tweaks, - [][musig2.PubNonceSize]byte, - ...musig2.SessionOption) (*MuSig2SessionInfo, error) { - - return nil, nil -} - -// MuSig2RegisterNonces registers one or more public nonces of other signing -// participants for a session identified by its ID. This method returns true -// once we have all nonces for all other signing participants. -func (m *MockSigner) MuSig2RegisterNonces(MuSig2SessionID, - [][musig2.PubNonceSize]byte) (bool, error) { - - return false, nil -} - -// MuSig2Sign creates a partial signature using the local signing key -// that was specified when the session was created. This can only be -// called when all public nonces of all participants are known and have -// been registered with the session. If this node isn't responsible for -// combining all the partial signatures, then the cleanup parameter -// should be set, indicating that the session can be removed from memory -// once the signature was produced. -func (m *MockSigner) MuSig2Sign(MuSig2SessionID, - [sha256.Size]byte, bool) (*musig2.PartialSignature, error) { - - return nil, nil -} - -// MuSig2CombineSig combines the given partial signature(s) with the -// local one, if it already exists. Once a partial signature of all -// participants is registered, the final signature will be combined and -// returned. -func (m *MockSigner) MuSig2CombineSig(MuSig2SessionID, - []*musig2.PartialSignature) (*schnorr.Signature, bool, error) { - - return nil, false, nil -} - -// MuSig2Cleanup removes a session from memory to free up resources. -func (m *MockSigner) MuSig2Cleanup(MuSig2SessionID) error { - return nil -} - -// findKey searches through all stored private keys and returns one -// corresponding to the hashed pubkey if it can be found. The public key may -// either correspond directly to the private key or to the private key with a -// tweak applied. -func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte, - doubleTweak *btcec.PrivateKey) *btcec.PrivateKey { - - for _, privkey := range m.Privkeys { - // First check whether public key is directly derived from private key. - hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) - if bytes.Equal(hash160, needleHash160) { - return privkey - } - - // Otherwise check if public key is derived from tweaked private key. - switch { - case singleTweak != nil: - privkey = TweakPrivKey(privkey, singleTweak) - case doubleTweak != nil: - privkey = DeriveRevocationPrivKey(privkey, doubleTweak) - default: - continue - } - hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) - if bytes.Equal(hash160, needleHash160) { - return privkey - } - } - return nil -} - -// pubkeyFromHex parses a Bitcoin public key from a hex encoded string. -func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { - bytes, err := hex.DecodeString(keyHex) - if err != nil { - return nil, err - } - return btcec.ParsePubKey(bytes) -} - -// privkeyFromHex parses a Bitcoin private key from a hex encoded string. -func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) { - bytes, err := hex.DecodeString(keyHex) - if err != nil { - return nil, err - } - key, _ := btcec.PrivKeyFromBytes(bytes) - return key, nil - -} - -// pubkeyToHex serializes a Bitcoin public key to a hex encoded string. -func pubkeyToHex(key *btcec.PublicKey) string { - return hex.EncodeToString(key.SerializeCompressed()) -} - -// privkeyFromHex serializes a Bitcoin private key to a hex encoded string. -func privkeyToHex(key *btcec.PrivateKey) string { - return hex.EncodeToString(key.Serialize()) -} diff --git a/lntest/mock/signer.go b/lntest/mock/signer.go index 4dd465cd878..6b17cbf667d 100644 --- a/lntest/mock/signer.go +++ b/lntest/mock/signer.go @@ -196,7 +196,7 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, // submitted as well to reduce the number of method calls necessary later on. func (s *SingleSigner) MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks, - [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) { + [][musig2.PubNonceSize]byte, ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { return nil, nil } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index eca08eb37e3..50c92b93434 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -25,11 +25,11 @@ import ( "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/blockcache" - "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/musession" ) const ( @@ -109,8 +109,7 @@ type BtcWallet struct { blockCache *blockcache.BlockCache - musig2Sessions map[input.MuSig2SessionID]*muSig2State - musig2SessionsMtx sync.Mutex + *musession.Manager } // A compile time check to ensure that BtcWallet implements the @@ -172,16 +171,21 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) { } } - return &BtcWallet{ - cfg: &cfg, - wallet: wallet, - db: wallet.Database(), - chain: cfg.ChainSource, - netParams: cfg.NetParams, - chainKeyScope: chainKeyScope, - blockCache: blockCache, - musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State), - }, nil + finalWallet := &BtcWallet{ + cfg: &cfg, + wallet: wallet, + db: wallet.Database(), + chain: cfg.ChainSource, + netParams: cfg.NetParams, + chainKeyScope: chainKeyScope, + blockCache: blockCache, + } + + finalWallet.Manager = musession.NewManager( + finalWallet.fetchPrivKey, + ) + + return finalWallet, nil } // loaderCfg holds optional wallet loader configuration. diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index cdb1887dc78..e620458055e 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -457,282 +457,6 @@ func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx, }, nil } -// muSig2State is a struct that holds on to the internal signing session state -// of a MuSig2 session. -type muSig2State struct { - // MuSig2SessionInfo is the associated meta information of the signing - // session. - input.MuSig2SessionInfo - - // context is the signing context responsible for keeping track of the - // public keys involved in the signing process. - context *musig2.Context - - // session is the signing session responsible for keeping track of the - // nonces and partial signatures involved in the signing process. - session *musig2.Session -} - -// MuSig2CreateSession creates a new MuSig2 signing session using the local -// key identified by the key locator. The complete list of all public keys of -// all signing parties must be provided, including the public key of the local -// signing key. If nonces of other parties are already known, they can be -// submitted as well to reduce the number of method calls necessary later on. -// -// The set of sessionOpts are _optional_ and allow a caller to modify the -// generated sessions. As an example the local nonce might already be generated -// ahead of time. -func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator, - allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, - otherSignerNonces [][musig2.PubNonceSize]byte, - sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { - - // We need to derive the private key for signing. In the remote signing - // setup, this whole RPC call will be forwarded to the signing - // instance, which requires it to be stateful. - privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{ - KeyLocator: keyLoc, - }) - if err != nil { - return nil, fmt.Errorf("error deriving private key: %v", err) - } - - // The context keeps track of all signing keys and our local key. - allOpts := append( - []musig2.ContextOption{ - musig2.WithKnownSigners(allSignerPubKeys), - }, - tweaks.ToContextOptions()..., - ) - musigContext, err := musig2.NewContext(privKey, true, allOpts...) - if err != nil { - return nil, fmt.Errorf("error creating MuSig2 signing "+ - "context: %v", err) - } - - // The session keeps track of the own and other nonces. - musigSession, err := musigContext.NewSession(sessionOpts...) - if err != nil { - return nil, fmt.Errorf("error creating MuSig2 signing "+ - "session: %v", err) - } - - // TODO(roasbeef): actually want to expose the context for channels so - // don't always need to re-create? - - // Add all nonces we might've learned so far. - haveAllNonces := false - for _, otherSignerNonce := range otherSignerNonces { - haveAllNonces, err = musigSession.RegisterPubNonce( - otherSignerNonce, - ) - if err != nil { - return nil, fmt.Errorf("error registering other "+ - "signer public nonce: %v", err) - } - } - - // Register the new session. - combinedKey, err := musigContext.CombinedKey() - if err != nil { - return nil, fmt.Errorf("error getting combined key: %v", err) - } - session := &muSig2State{ - MuSig2SessionInfo: input.MuSig2SessionInfo{ - SessionID: input.NewMuSig2SessionID( - combinedKey, musigSession.PublicNonce(), - ), - PublicNonce: musigSession.PublicNonce(), - CombinedKey: combinedKey, - TaprootTweak: tweaks.HasTaprootTweak(), - HaveAllNonces: haveAllNonces, - }, - context: musigContext, - session: musigSession, - } - - // The internal key is only calculated if we are using a taproot tweak - // and need to know it for a potential script spend. - if tweaks.HasTaprootTweak() { - internalKey, err := musigContext.TaprootInternalKey() - if err != nil { - return nil, fmt.Errorf("error getting internal key: %v", - err) - } - session.TaprootInternalKey = internalKey - } - - // Since we generate new nonces for every session, there is no way that - // a session with the same ID already exists. So even if we call the API - // twice with the same signers, we still get a new ID. - b.musig2SessionsMtx.Lock() - b.musig2Sessions[session.SessionID] = session - b.musig2SessionsMtx.Unlock() - - return &session.MuSig2SessionInfo, nil -} - -// MuSig2RegisterNonces registers one or more public nonces of other signing -// participants for a session identified by its ID. This method returns true -// once we have all nonces for all other signing participants. -func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, - otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) { - - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - session, ok := b.musig2Sessions[sessionID] - if !ok { - return false, fmt.Errorf("session with ID %x not found", - sessionID[:]) - } - - // Make sure we don't exceed the number of expected nonces as that would - // indicate something is wrong with the signing setup. - if session.HaveAllNonces { - return true, fmt.Errorf("already have all nonces") - } - - numSigners := len(session.context.SigningKeys()) - remainingNonces := numSigners - session.session.NumRegisteredNonces() - if len(otherSignerNonces) > remainingNonces { - return false, fmt.Errorf("only %d other nonces remaining but "+ - "trying to register %d more", remainingNonces, - len(otherSignerNonces)) - } - - // Add all nonces we've learned so far. - var err error - for _, otherSignerNonce := range otherSignerNonces { - session.HaveAllNonces, err = session.session.RegisterPubNonce( - otherSignerNonce, - ) - if err != nil { - return false, fmt.Errorf("error registering other "+ - "signer public nonce: %v", err) - } - } - - return session.HaveAllNonces, nil -} - -// MuSig2Sign creates a partial signature using the local signing key -// that was specified when the session was created. This can only be -// called when all public nonces of all participants are known and have -// been registered with the session. If this node isn't responsible for -// combining all the partial signatures, then the cleanup parameter -// should be set, indicating that the session can be removed from memory -// once the signature was produced. -func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID, - msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) { - - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - session, ok := b.musig2Sessions[sessionID] - if !ok { - return nil, fmt.Errorf("session with ID %x not found", - sessionID[:]) - } - - // We can only sign once we have all other signer's nonces. - if !session.HaveAllNonces { - return nil, fmt.Errorf("only have %d of %d required nonces", - session.session.NumRegisteredNonces(), - len(session.context.SigningKeys())) - } - - // Create our own partial signature with the local signing key. - partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys()) - if err != nil { - return nil, fmt.Errorf("error signing with local key: %v", err) - } - - // Clean up our local state if requested. - if cleanUp { - delete(b.musig2Sessions, sessionID) - } - - return partialSig, nil -} - -// MuSig2CombineSig combines the given partial signature(s) with the -// local one, if it already exists. Once a partial signature of all -// participants is registered, the final signature will be combined and -// returned. -func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID, - partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool, - error) { - - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - session, ok := b.musig2Sessions[sessionID] - if !ok { - return nil, false, fmt.Errorf("session with ID %x not found", - sessionID[:]) - } - - // Make sure we don't exceed the number of expected partial signatures - // as that would indicate something is wrong with the signing setup. - if session.HaveAllSigs { - return nil, true, fmt.Errorf("already have all partial" + - "signatures") - } - - // Add all sigs we got so far. - var ( - finalSig *schnorr.Signature - err error - ) - for _, otherPartialSig := range partialSigs { - session.HaveAllSigs, err = session.session.CombineSig( - otherPartialSig, - ) - if err != nil { - return nil, false, fmt.Errorf("error combining "+ - "partial signature: %v", err) - } - } - - // If we have all partial signatures, we should be able to get the - // complete signature now. We also remove this session from memory since - // there is nothing more left to do. - if session.HaveAllSigs { - finalSig = session.session.FinalSig() - delete(b.musig2Sessions, sessionID) - } - - return finalSig, session.HaveAllSigs, nil -} - -// MuSig2Cleanup removes a session from memory to free up resources. -func (b *BtcWallet) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - _, ok := b.musig2Sessions[sessionID] - if !ok { - return fmt.Errorf("session with ID %x not found", sessionID[:]) - } - - delete(b.musig2Sessions, sessionID) - - return nil -} - // A compile time check to ensure that BtcWallet implements the Signer // interface. var _ input.Signer = (*BtcWallet)(nil) diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index c1a9b5ccfdc..4ced34eb1a1 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -1,9 +1,11 @@ package lnwallet import ( + "bytes" "crypto/rand" "encoding/binary" "encoding/hex" + "fmt" "io" "io/ioutil" prand "math/rand" @@ -11,13 +13,18 @@ import ( "os" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/musession" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" ) @@ -348,8 +355,8 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( Packager: channeldb.NewChannelPackager(shortChanID), } - aliceSigner := &input.MockSigner{Privkeys: aliceKeys} - bobSigner := &input.MockSigner{Privkeys: bobKeys} + aliceSigner := NewMockSigner(aliceKeys, nil) + bobSigner := NewMockSigner(bobKeys, nil) // TODO(roasbeef): make mock version of pre-image store @@ -425,6 +432,27 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( // network by populating the initial revocation windows of the passed // commitment state machines. func initRevocationWindows(chanA, chanB *LightningChannel) error { + // If these are taproot chanenls, then we need to also simulate sending + // either FundingLocked or ChannelReestablish by calling + // InitRemoteMusigNonces for both sides. + if chanA.channelState.ChanType.IsTaproot() { + chanANonces, err := chanA.GenMusigNonces() + if err != nil { + return err + } + chanBNonces, err := chanB.GenMusigNonces() + if err != nil { + return err + } + + if err := chanA.InitRemoteMusigNonces(chanBNonces); err != nil { + return err + } + if err := chanB.InitRemoteMusigNonces(chanANonces); err != nil { + return err + } + } + aliceNextRevoke, err := chanA.NextRevocationKey() if err != nil { return err @@ -537,3 +565,211 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { return nil } + +// MockSigner is a simple implementation of the Signer interface. Each one has +// a set of private keys in a slice and can sign messages using the appropriate +// one. +type MockSigner struct { + Privkeys []*btcec.PrivateKey + NetParams *chaincfg.Params + + *musession.Manager +} + +var _ input.Signer = (*MockSigner)(nil) + +func NewMockSigner(privKeys []*btcec.PrivateKey, netParams *chaincfg.Params) *MockSigner { + signer := &MockSigner{ + Privkeys: privKeys, + NetParams: netParams, + } + + keyFetcher := func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) { + return signer.Privkeys[0], nil + } + signer.Manager = musession.NewManager(keyFetcher) + + return signer +} + +// SignOutputRaw generates a signature for the passed transaction according to +// the data within the passed SignDescriptor. +func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, + signDesc *input.SignDescriptor) (input.Signature, error) { + + pubkey := signDesc.KeyDesc.PubKey + switch { + case signDesc.SingleTweak != nil: + pubkey = input.TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak) + case signDesc.DoubleTweak != nil: + pubkey = input.DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) + } + + hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) + privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("mock signer does not have key") + } + + // In case of a taproot output any signature is always a Schnorr + // signature, based on the new tapscript sighash algorithm. + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + sigHashes := txscript.NewTxSigHashes( + tx, signDesc.PrevOutputFetcher, + ) + + // Are we spending a script path or the key path? The API is + // slightly different, so we need to account for that to get + // the raw signature. + var ( + rawSig []byte + err error + ) + switch signDesc.SignMethod { + case input.TaprootKeySpendBIP0086SignMethod, + input.TaprootKeySpendSignMethod: + + // This function tweaks the private key using the tap + // root key supplied as the tweak. + rawSig, err = txscript.RawTxInTaprootSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + signDesc.TapTweak, signDesc.HashType, + privKey, + ) + if err != nil { + return nil, err + } + + case input.TaprootScriptSpendSignMethod: + leaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: signDesc.WitnessScript, + } + rawSig, err = txscript.RawTxInTapscriptSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + leaf, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + } + + // The signature returned above might have a sighash flag + // attached if a non-default type was used. We'll slice this + // off if it exists to ensure we can properly parse the raw + // signature. + sig, err := schnorr.ParseSignature( + rawSig[:schnorr.SignatureSize], + ) + if err != nil { + return nil, err + } + + return sig, nil + } + + sig, err := txscript.RawTxInWitnessSignature( + tx, signDesc.SigHashes, signDesc.InputIndex, signDesc.Output.Value, + signDesc.WitnessScript, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + + return ecdsa.ParseDERSignature(sig[:len(sig)-1]) +} + +// ComputeInputScript generates a complete InputIndex for the passed transaction +// with the signature as defined within the passed SignDescriptor. This method +// should be capable of generating the proper input script for both regular +// p2wkh output and p2wkh outputs nested within a regular p2sh output. +func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *input.SignDescriptor) (*input.Script, error) { + scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs( + signDesc.Output.PkScript, m.NetParams) + if err != nil { + return nil, err + } + + switch scriptType { + case txscript.PubKeyHashTy: + privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, + signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("mock signer does not have key for "+ + "address %v", addresses[0]) + } + + sigScript, err := txscript.SignatureScript( + tx, signDesc.InputIndex, signDesc.Output.PkScript, + txscript.SigHashAll, privKey, true, + ) + if err != nil { + return nil, err + } + + return &input.Script{SigScript: sigScript}, nil + + case txscript.WitnessV0PubKeyHashTy: + privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, + signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("mock signer does not have key for "+ + "address %v", addresses[0]) + } + + witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, + signDesc.InputIndex, signDesc.Output.Value, + signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) + if err != nil { + return nil, err + } + + return &input.Script{Witness: witnessScript}, nil + + default: + return nil, fmt.Errorf("unexpected script type: %v", scriptType) + } +} + +// findKey searches through all stored private keys and returns one +// corresponding to the hashed pubkey if it can be found. The public key may +// either correspond directly to the private key or to the private key with a +// tweak applied. +func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte, + doubleTweak *btcec.PrivateKey) *btcec.PrivateKey { + + for _, privkey := range m.Privkeys { + // First check whether public key is directly derived from private key. + hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) + if bytes.Equal(hash160, needleHash160) { + return privkey + } + + // Otherwise check if public key is derived from tweaked private key. + switch { + case singleTweak != nil: + privkey = input.TweakPrivKey(privkey, singleTweak) + case doubleTweak != nil: + privkey = input.DeriveRevocationPrivKey(privkey, doubleTweak) + default: + continue + } + hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) + if bytes.Equal(hash160, needleHash160) { + return privkey + } + } + return nil +} + +// pubkeyToHex serializes a Bitcoin public key to a hex encoded string. +func pubkeyToHex(key *btcec.PublicKey) string { + return hex.EncodeToString(key.SerializeCompressed()) +} + +// privkeyFromHex serializes a Bitcoin private key to a hex encoded string. +func privkeyToHex(key *btcec.PrivateKey) string { + return hex.EncodeToString(key.Serialize()) +} diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index d95486c7648..092b6744ef8 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -536,7 +536,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { remoteCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub) localCommitTweak := input.SingleTweakBytes(commitPoint, bobKeyPub) - aliceSelfOutputSigner := &input.MockSigner{ + aliceSelfOutputSigner := &MockSigner{ Privkeys: []*btcec.PrivateKey{aliceKeyPriv}, } @@ -628,7 +628,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { t.Fatalf("spend from delay output is invalid: %v", err) } - localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} + localSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} // Next, we'll test bob spending with the derived revocation key to // simulate the scenario when Alice broadcasts this commitment @@ -949,12 +949,12 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp } // Create mock signers that can sign for the keys that are used. - localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{ + localSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{ tc.localPaymentBasepointSecret, tc.localDelayedPaymentBasepointSecret, tc.localFundingPrivkey, localDummy1, localDummy2, }} - remoteSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{ + remoteSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{ tc.remoteFundingPrivkey, tc.remoteRevocationBasepointSecret, tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2, }} From 1797ace901f7cf9304625780ba04e83b10a77c23 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:31:16 -0800 Subject: [PATCH 48/87] multi: morph existing lnwire.SIg into new struct to support schnorr+ecdsa In this commit, we somewhat haphazardly morph the existing [64]byte sig type into a new struct that contains a sig type enum. Using this, we're able to utilize the input.Signature in most places to support handling a 64 byte signature that can be interpreted as either an ECDSA signature or a schnorr signature. The tradeoff here is that in certain situations a caller will need to "force" the sig to be a schnorr signature so the parsing types work. Alternatively, we can do something along the lines of making lnwire.Sig an interface, or alternatively add type params to the various messages that carry sigs. --- channeldb/graph.go | 2 +- discovery/gossiper.go | 8 +- lnrpc/routerrpc/router_backend.go | 2 +- lnrpc/signrpc/signer_server.go | 2 +- lnwallet/rpcwallet/rpcwallet.go | 20 ++- lnwallet/test/test_interface.go | 2 +- lnwire/lnwire.go | 4 +- lnwire/signature.go | 213 ++++++++++++++++++++++------- lnwire/signature_test.go | 28 ++-- lnwire/writer.go | 2 +- lnwire/writer_test.go | 12 +- netann/channel_announcement.go | 8 +- netann/channel_update.go | 4 +- server.go | 2 +- watchtower/blob/justice_kit.go | 68 +++++---- watchtower/wtclient/backup_task.go | 8 +- zpay32/decode.go | 8 +- zpay32/encode.go | 11 +- 18 files changed, 277 insertions(+), 127 deletions(-) diff --git a/channeldb/graph.go b/channeldb/graph.go index 957c0b828d4..5107cccd17b 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -2724,7 +2724,7 @@ func (l *LightningNode) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement, return nodeAnn, nil } - sig, err := lnwire.NewSigFromRawSignature(l.AuthSigBytes) + sig, err := lnwire.NewSigFromECDSARawSignature(l.AuthSigBytes) if err != nil { return nil, err } diff --git a/discovery/gossiper.go b/discovery/gossiper.go index bceb3755c1e..b1cf9c3fc40 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2049,25 +2049,25 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, BitcoinKey2: info.BitcoinKey2Bytes, ExtraOpaqueData: edge.ExtraOpaqueData, } - chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.NodeSig1Bytes, ) if err != nil { return nil, nil, err } - chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.NodeSig2Bytes, ) if err != nil { return nil, nil, err } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.BitcoinSig1Bytes, ) if err != nil { return nil, nil, err } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.BitcoinSig2Bytes, ) if err != nil { diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index a372af521cd..b9104be9488 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -1304,7 +1304,7 @@ func marshallChannelUpdate(update *lnwire.ChannelUpdate) *lnrpc.ChannelUpdate { } return &lnrpc.ChannelUpdate{ - Signature: update.Signature[:], + Signature: update.Signature.RawBytes(), ChainHash: update.ChainHash[:], ChanId: update.ShortChannelID.ToUint64(), Timestamp: update.Timestamp, diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 7c098f25704..7ddd39f147f 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -713,7 +713,7 @@ func (s *Server) VerifyMessage(_ context.Context, } // The signature must be fixed-size LN wire format encoded. - wireSig, err := lnwire.NewSigFromRawSignature(in.Signature) + wireSig, err := lnwire.NewSigFromECDSARawSignature(in.Signature) if err != nil { return nil, fmt.Errorf("failed to decode signature: %v", err) } diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index b2b8bbfc56f..b1555b38b5c 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -383,8 +383,8 @@ func (r *RPCKeyRing) DeriveKey( // sha256 of the resulting shared point serialized in compressed format. If k is // our private key, and P is the public key, we perform the following operation: // -// sx := k*P -// s := sha256(sx.SerializeCompressed()) +// sx := k*P +// s := sha256(sx.SerializeCompressed()) // // NOTE: This method is part of the keychain.ECDHRing interface. func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, @@ -445,11 +445,16 @@ func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator, "signer instance: %v", err) } - wireSig, err := lnwire.NewSigFromRawSignature(resp.Signature) + wireSig, err := lnwire.NewSigFromECDSARawSignature(resp.Signature) if err != nil { return nil, fmt.Errorf("error parsing raw signature: %v", err) } - return wireSig.ToSignature() + ecdsaSig, err := wireSig.ToSignature() + if err != nil { + return nil, err + } + + return ecdsaSig.(*ecdsa.Signature), nil } // SignMessageCompact signs the given message, single or double SHA256 hashing @@ -638,8 +643,8 @@ func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx, // submitted as well to reduce the number of method calls necessary later on. func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, - otherNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, - error) { + otherNonces [][musig2.PubNonceSize]byte, + sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { // We need to serialize all data for the RPC call. We can do that by // putting everything directly into the request struct. @@ -674,6 +679,9 @@ func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator, } } + // TODO(roasbeef): extend RPC call w/ extra options needed for taproot + // musig2 session creation + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index da3e3ed029f..ea5cd20f564 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -927,7 +927,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, // by having Alice immediately process his contribution. err = aliceChanReservation.ProcessContribution(bobContribution) if err != nil { - t.Fatalf("alice unable to process bob's contribution") + t.Fatalf("alice unable to process bob's contribution: %v", err) } assertContributionInitPopulated(t, bobChanReservation.TheirContribution()) diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 0361d76484f..df01c180f00 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -171,7 +171,7 @@ func WriteElement(w *bytes.Buffer, element interface{}) error { } case Sig: // Write buffer - if _, err := w.Write(e[:]); err != nil { + if _, err := w.Write(e.bytes[:]); err != nil { return err } case PingPayload: @@ -575,7 +575,7 @@ func ReadElement(r io.Reader, element interface{}) error { *e = sigs case *Sig: - if _, err := io.ReadFull(r, e[:]); err != nil { + if _, err := io.ReadFull(r, e.bytes[:]); err != nil { return err } case *OpaqueReason: diff --git a/lnwire/signature.go b/lnwire/signature.go index f0bed72cb3f..c5c18e455cb 100644 --- a/lnwire/signature.go +++ b/lnwire/signature.go @@ -5,15 +5,10 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/lightningnetwork/lnd/input" ) -// Sig is a fixed-sized ECDSA signature. Unlike Bitcoin, we use fixed sized -// signatures on the wire, instead of DER encoded signatures. This type -// provides several methods to convert to/from a regular Bitcoin DER encoded -// signature (raw bytes and *ecdsa.Signature). -type Sig [64]byte - var ( errSigTooShort = errors.New("malformed signature: too short") errBadLength = errors.New("malformed signature: bad length") @@ -23,14 +18,71 @@ var ( errSTooLong = errors.New("S is over 32 bytes long without padding") ) -// NewSigFromRawSignature returns a Sig from a Bitcoin raw signature encoded in -// the canonical DER encoding. -func NewSigFromRawSignature(sig []byte) (Sig, error) { - var b Sig +// sigType... +type sigType uint + +const ( + // sigTypeECDSA... + sigTypeECDSA sigType = iota + + // sigTypeSchnorr... + sigTypeSchnorr +) + +// TODO(roasbef): make into interface after all? + +// Sig is a fixed-sized ECDSA signature or 64-byte schnorr signature. For the +// ECDSA sig, unlike Bitcoin, we use fixed sized signatures on the wire, +// instead of DER encoded signatures. This type provides several methods to +// convert to/from a regular Bitcoin DER encoded signature (raw bytes and +// *ecdsa.Signature). +type Sig struct { + bytes [64]byte + + sigType sigType +} + +// ForceSchnorr... +func (s *Sig) ForceSchnorr() { + s.sigType = sigTypeSchnorr +} + +// RawBytes... +func (s *Sig) RawBytes() []byte { + return s.bytes[:] +} + +// Copy... +func (s *Sig) Copy() Sig { + var sCopy Sig + copy(sCopy.bytes[:], s.bytes[:]) + sCopy.sigType = s.sigType + + return sCopy +} + +// NewSigFromWireECDSA returns a Sig instance based on an ECDSA signature +// that's already in the 64-byte format we expect. +func NewSigFromWireECDSA(sig []byte) (Sig, error) { + if len(sig) != 64 { + return Sig{}, fmt.Errorf("%w: %v bytes", errSigTooShort, + len(sig)) + } + + var s Sig + copy(s.bytes[:], sig) + + return s, nil +} + +// NewSigFromECDSARawSignature returns a Sig from a Bitcoin raw signature +// encoded in the canonical DER encoding. +func NewSigFromECDSARawSignature(sig []byte) (Sig, error) { + var b [64]byte // Check the total length is above the minimal. if len(sig) < ecdsa.MinSigLen { - return b, errSigTooShort + return Sig{}, errSigTooShort } // The DER representation is laid out as: @@ -46,7 +98,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // siglen should be less than the entire message and greater than // the minimal message size. if sigLen+2 > len(sig) || sigLen+2 < ecdsa.MinSigLen { - return b, errBadLength + return Sig{}, errBadLength } // Reading , remaining: [r 0x02 s] @@ -56,7 +108,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // Assuming s is one byte, then we have 0x30, , 0x20, // , 0x20, , s, a total of 7 bytes. if rLen <= 0 || rLen+7 > len(sig) { - return b, errBadRLength + return Sig{}, errBadRLength } // Reading , remaining: [s] @@ -67,7 +119,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // We know r is rLen bytes, and we have 0x30, , 0x20, // , 0x20, , a total of rLen+6 bytes. if sLen <= 0 || sLen+rLen+6 > len(sig) { - return b, errBadSLength + return Sig{}, errBadSLength } // Check to make sure R and S can both fit into their intended buffers. @@ -78,7 +130,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // check S first. if sLen > 32 { if (sLen > 33) || (sig[6+rLen] != 0x00) { - return b, errSTooLong + return Sig{}, errSTooLong } sLen-- copy(b[64-sLen:], sig[7+rLen:]) @@ -89,7 +141,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // Do the same for R as we did for S if rLen > 32 { if (rLen > 33) || (sig[4] != 0x00) { - return b, errRTooLong + return Sig{}, errRTooLong } rLen-- copy(b[32-rLen:], sig[5:5+rLen]) @@ -97,11 +149,24 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { copy(b[32-rLen:], sig[4:4+rLen]) } - return b, nil + return Sig{ + bytes: b, + sigType: sigTypeECDSA, + }, nil +} + +// NewSigFromSchnorrRawSignature converts a raw schnorr signature into an +// lnwire.Sig. +func NewSigFromSchnorrRawSignature(sig []byte) (Sig, error) { + var s Sig + copy(s.bytes[:], sig) + s.sigType = sigTypeSchnorr + + return s, nil } // NewSigFromSignature creates a new signature as used on the wire, from an -// existing ecdsa.Signature. +// existing ecdsa.Signature or schnorr.Signature. func NewSigFromSignature(e input.Signature) (Sig, error) { if e == nil { return Sig{}, fmt.Errorf("cannot decode empty signature") @@ -113,45 +178,91 @@ func NewSigFromSignature(e input.Signature) (Sig, error) { return Sig{}, fmt.Errorf("cannot decode empty signature") } - // Serialize the signature with all the checks that entails. - return NewSigFromRawSignature(e.Serialize()) -} + switch ecSig := e.(type) { + // If this is a schnorr signature, then we can just pack it as normal, + // since the default encoding is already 64 bytes. + case *schnorr.Signature: + var sigBytes [64]byte + copy(sigBytes[:], e.Serialize()) + + return Sig{ + bytes: sigBytes, + sigType: sigTypeSchnorr, + }, nil + + // For ECDSA signatures, we'll need to do a bit more work to map the + // signature into a compact 64 byte form. + case *ecdsa.Signature: + // Serialize the signature with all the checks that entails. + return NewSigFromECDSARawSignature(e.Serialize()) -// ToSignature converts the fixed-sized signature to a ecdsa.Signature objects -// which can be used for signature validation checks. -func (b *Sig) ToSignature() (*ecdsa.Signature, error) { - // Parse the signature with strict checks. - sigBytes := b.ToSignatureBytes() - sig, err := ecdsa.ParseDERSignature(sigBytes) - if err != nil { - return nil, err + default: + return Sig{}, fmt.Errorf("unknown wire sig type: %T", ecSig) } +} + +// ToSignature converts the fixed-sized signature to a input.Signature which +// can be used for signature validation checks. +func (b *Sig) ToSignature() (input.Signature, error) { + switch b.sigType { + case sigTypeSchnorr: + return schnorr.ParseSignature(b.bytes[:]) - return sig, nil + case sigTypeECDSA: + // Parse the signature with strict checks. + sigBytes := b.ToSignatureBytes() + sig, err := ecdsa.ParseDERSignature(sigBytes) + if err != nil { + return nil, err + } + + return sig, nil + + default: + return nil, fmt.Errorf("unknown sig type: %v", b.sigType) + } } -// ToSignatureBytes serializes the target fixed-sized signature into the raw -// bytes of a DER encoding. +// ToSignatureBytes serializes the target fixed-sized signature into the +// encoding of the primary domain for the signature. For ECDSA signatures, this +// is the raw bytes of a DER encoding. func (b *Sig) ToSignatureBytes() []byte { - // Extract canonically-padded bigint representations from buffer - r := extractCanonicalPadding(b[0:32]) - s := extractCanonicalPadding(b[32:64]) - rLen := uint8(len(r)) - sLen := uint8(len(s)) - - // Create a canonical serialized signature. DER format is: - // 0x30 0x02 r 0x02 s - sigBytes := make([]byte, 6+rLen+sLen) - sigBytes[0] = 0x30 // DER signature magic value - sigBytes[1] = 4 + rLen + sLen // Length of rest of signature - sigBytes[2] = 0x02 // Big integer magic value - sigBytes[3] = rLen // Length of R - sigBytes[rLen+4] = 0x02 // Big integer magic value - sigBytes[rLen+5] = sLen // Length of S - copy(sigBytes[4:], r) // Copy R - copy(sigBytes[rLen+6:], s) // Copy S - - return sigBytes + switch b.sigType { + // For ECDSA signatures, we'll convert to DER encoding. + case sigTypeECDSA: + // Extract canonically-padded bigint representations from buffer + r := extractCanonicalPadding(b.bytes[0:32]) + s := extractCanonicalPadding(b.bytes[32:64]) + rLen := uint8(len(r)) + sLen := uint8(len(s)) + + // Create a canonical serialized signature. DER format is: + // 0x30 0x02 r 0x02 s + sigBytes := make([]byte, 6+rLen+sLen) + sigBytes[0] = 0x30 // DER signature magic value + sigBytes[1] = 4 + rLen + sLen // Length of rest of signature + sigBytes[2] = 0x02 // Big integer magic value + sigBytes[3] = rLen // Length of R + sigBytes[rLen+4] = 0x02 // Big integer magic value + sigBytes[rLen+5] = sLen // Length of S + copy(sigBytes[4:], r) // Copy R + copy(sigBytes[rLen+6:], s) // Copy S + + return sigBytes + + // For schnorr signatures, we can use the same internal 64 bytes. + case sigTypeSchnorr: + // We'll make a copy of the signature so we don't return a + // refrence into the raw slice. + var sig [64]byte + copy(sig[:], b.bytes[:]) + return sig[:] + + default: + // TODO(roasbeef): can only be called via public methods so + // never reachable? + panic("sig type not set") + } } // extractCanonicalPadding is a utility function to extract the canonical diff --git a/lnwire/signature_test.go b/lnwire/signature_test.go index 48ce212a05d..eee5eb4a077 100644 --- a/lnwire/signature_test.go +++ b/lnwire/signature_test.go @@ -21,11 +21,13 @@ func TestSignatureSerializeDeserialize(t *testing.T) { return err } - e2, err := sig.ToSignature() + e2Input, err := sig.ToSignature() if err != nil { return err } + e2 := e2Input.(*ecdsa.Signature) + if !e.IsEqual(e2) { return fmt.Errorf("pre/post-serialize sigs don't " + "match") @@ -188,16 +190,18 @@ func TestNewSigFromRawSignature(t *testing.T) { rawSig: normalSig, expectedErr: nil, expectedSig: Sig{ - // r value - 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, - 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, - 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, - 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, - // s value - 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, - 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, - 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, - 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + bytes: [64]byte{ + // r value + 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, + 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, + 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, + 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, + // s value + 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, + 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, + 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + }, }, }, { @@ -266,7 +270,7 @@ func TestNewSigFromRawSignature(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - result, err := NewSigFromRawSignature(tc.rawSig) + result, err := NewSigFromECDSARawSignature(tc.rawSig) require.Equal(t, tc.expectedErr, err) require.Equal(t, tc.expectedSig, result) }) diff --git a/lnwire/writer.go b/lnwire/writer.go index 5b99ab368a6..f3397881d90 100644 --- a/lnwire/writer.go +++ b/lnwire/writer.go @@ -154,7 +154,7 @@ func WriteShortChannelID(buf *bytes.Buffer, shortChanID ShortChannelID) error { // WriteSig appends the signature to the provided buffer. func WriteSig(buf *bytes.Buffer, sig Sig) error { - return WriteBytes(buf, sig[:]) + return WriteBytes(buf, sig.bytes[:]) } // WriteSigs appends the slice of signatures to the provided buffer with its diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go index 68594e59a45..185685c4132 100644 --- a/lnwire/writer_test.go +++ b/lnwire/writer_test.go @@ -147,7 +147,9 @@ func TestWriteShortChannelID(t *testing.T) { func TestWriteSig(t *testing.T) { buf := new(bytes.Buffer) - data := Sig{1, 2, 3} + data := Sig{ + bytes: [64]byte{1, 2, 3}, + } expectedBytes := [64]byte{1, 2, 3} err := WriteSig(buf, data) @@ -158,14 +160,14 @@ func TestWriteSig(t *testing.T) { func TestWriteSigs(t *testing.T) { buf := new(bytes.Buffer) - sig1, sig2, sig3 := Sig{1}, Sig{2}, Sig{3} + sig1, sig2, sig3 := Sig{bytes: [64]byte{1}}, Sig{bytes: [64]byte{2}}, Sig{bytes: [64]byte{3}} data := []Sig{sig1, sig2, sig3} // First two bytes encode the length of the slice. expectedBytes := []byte{0, 3} - expectedBytes = append(expectedBytes, sig1[:]...) - expectedBytes = append(expectedBytes, sig2[:]...) - expectedBytes = append(expectedBytes, sig3[:]...) + expectedBytes = append(expectedBytes, sig1.bytes[:]...) + expectedBytes = append(expectedBytes, sig2.bytes[:]...) + expectedBytes = append(expectedBytes, sig3.bytes[:]...) err := WriteSigs(buf, data) diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go index 480b8cf3e87..0ae8d606d4b 100644 --- a/netann/channel_announcement.go +++ b/netann/channel_announcement.go @@ -36,25 +36,25 @@ func CreateChanAnnouncement(chanProof *channeldb.ChannelAuthProof, if err != nil { return nil, nil, nil, err } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( chanProof.BitcoinSig1Bytes, ) if err != nil { return nil, nil, nil, err } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( chanProof.BitcoinSig2Bytes, ) if err != nil { return nil, nil, nil, err } - chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( chanProof.NodeSig1Bytes, ) if err != nil { return nil, nil, nil, err } - chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( chanProof.NodeSig2Bytes, ) if err != nil { diff --git a/netann/channel_update.go b/netann/channel_update.go index ca26acac678..b6555f37b1b 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -143,7 +143,9 @@ func ChannelUpdateFromEdge(info *channeldb.ChannelEdgeInfo, update := UnsignedChannelUpdateFromEdge(info, policy) var err error - update.Signature, err = lnwire.NewSigFromRawSignature(policy.SigBytes) + update.Signature, err = lnwire.NewSigFromECDSARawSignature( + policy.SigBytes, + ) if err != nil { return nil, err } diff --git a/server.go b/server.go index cf8e8a52ba2..b6d1950c642 100644 --- a/server.go +++ b/server.go @@ -827,7 +827,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, "self node announcement: %v", err) } selfNode.AuthSigBytes = authSig.Serialize() - nodeAnn.Signature, err = lnwire.NewSigFromRawSignature( + nodeAnn.Signature, err = lnwire.NewSigFromECDSARawSignature( selfNode.AuthSigBytes, ) if err != nil { diff --git a/watchtower/blob/justice_kit.go b/watchtower/blob/justice_kit.go index 56b8ae4d546..7b39eeb4083 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -43,9 +43,10 @@ const ( ) // Size returns the size of the encoded-and-encrypted blob in bytes. -// nonce: 24 bytes -// enciphered plaintext: n bytes -// MAC: 16 bytes +// +// nonce: 24 bytes +// enciphered plaintext: n bytes +// MAC: 16 bytes func Size(blobType Type) int { return NonceSize + PlaintextSize(blobType) + CiphertextExpansion } @@ -173,7 +174,8 @@ func (b *JusticeKit) CommitToLocalWitnessScript() ([]byte, error) { // CommitToLocalRevokeWitnessStack constructs a witness stack spending the // revocation clause of the commitment to-local output. -// 1 +// +// 1 func (b *JusticeKit) CommitToLocalRevokeWitnessStack() ([][]byte, error) { toLocalSig, err := b.CommitToLocalSig.ToSignature() if err != nil { @@ -220,7 +222,8 @@ func (b *JusticeKit) CommitToRemoteWitnessScript() ([]byte, error) { // CommitToRemoteWitnessStack returns a witness stack spending the commitment // to-remote output, which consists of a single signature satisfying either the // legacy or anchor witness scripts. -// +// +// func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) { toRemoteSig, err := b.CommitToRemoteSig.ToSignature() if err != nil { @@ -345,14 +348,15 @@ func (b *JusticeKit) decode(r io.Reader, blobType Type) error { // constant-size plaintext size of 274 bytes. // // blob version 0 plaintext encoding: -// sweep address length: 1 byte -// padded sweep address: 42 bytes -// revocation pubkey: 33 bytes -// local delay pubkey: 33 bytes -// csv delay: 4 bytes -// commit to-local revocation sig: 64 bytes -// commit to-remote pubkey: 33 bytes, maybe blank -// commit to-remote sig: 64 bytes, maybe blank +// +// sweep address length: 1 byte +// padded sweep address: 42 bytes +// revocation pubkey: 33 bytes +// local delay pubkey: 33 bytes +// csv delay: 4 bytes +// commit to-local revocation sig: 64 bytes +// commit to-remote pubkey: 33 bytes, maybe blank +// commit to-remote sig: 64 bytes, maybe blank func (b *JusticeKit) encodeV0(w io.Writer) error { // Assert the sweep address length is sane. if len(b.SweepAddress) > MaxSweepAddrSize { @@ -394,7 +398,7 @@ func (b *JusticeKit) encodeV0(w io.Writer) error { } // Write 64-byte revocation signature for commit to-local output. - _, err = w.Write(b.CommitToLocalSig[:]) + _, err = w.Write(b.CommitToLocalSig.RawBytes()) if err != nil { return err } @@ -406,7 +410,7 @@ func (b *JusticeKit) encodeV0(w io.Writer) error { } // Write 64-byte commit to-remote signature, which may be blank. - _, err = w.Write(b.CommitToRemoteSig[:]) + _, err = w.Write(b.CommitToRemoteSig.RawBytes()) return err } @@ -416,14 +420,15 @@ func (b *JusticeKit) encodeV0(w io.Writer) error { // to-remote output. // // blob version 0 plaintext encoding: -// sweep address length: 1 byte -// padded sweep address: 42 bytes -// revocation pubkey: 33 bytes -// local delay pubkey: 33 bytes -// csv delay: 4 bytes -// commit to-local revocation sig: 64 bytes -// commit to-remote pubkey: 33 bytes, maybe blank -// commit to-remote sig: 64 bytes, maybe blank +// +// sweep address length: 1 byte +// padded sweep address: 42 bytes +// revocation pubkey: 33 bytes +// local delay pubkey: 33 bytes +// csv delay: 4 bytes +// commit to-local revocation sig: 64 bytes +// commit to-remote pubkey: 33 bytes, maybe blank +// commit to-remote sig: 64 bytes, maybe blank func (b *JusticeKit) decodeV0(r io.Reader) error { // Read the sweep address length as a single byte. var sweepAddrLen uint8 @@ -467,14 +472,20 @@ func (b *JusticeKit) decodeV0(r io.Reader) error { } // Read 64-byte revocation signature for commit to-local output. - _, err = io.ReadFull(r, b.CommitToLocalSig[:]) + var localSig [64]byte + _, err = io.ReadFull(r, localSig[:]) + if err != nil { + return err + } + + b.CommitToLocalSig, err = lnwire.NewSigFromWireECDSA(localSig[:]) if err != nil { return err } var ( commitToRemotePubkey PubKey - commitToRemoteSig lnwire.Sig + commitToRemoteSig [64]byte ) // Read 33-byte commit to-remote public key, which may be discarded. @@ -493,7 +504,12 @@ func (b *JusticeKit) decodeV0(r io.Reader) error { // valid compressed public key was read from the reader. if btcec.IsCompressedPubKey(commitToRemotePubkey[:]) { b.CommitToRemotePubKey = commitToRemotePubkey - b.CommitToRemoteSig = commitToRemoteSig + b.CommitToRemoteSig, err = lnwire.NewSigFromWireECDSA( + commitToRemoteSig[:], + ) + if err != nil { + return err + } } return nil diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index a7268930340..516e87e0d23 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -365,24 +365,24 @@ func (t *backupTask) craftSessionPayload( // Re-encode the DER signature into a fixed-size 64 byte // signature. - signature, err := lnwire.NewSigFromRawSignature(rawSignature) + signature, err := lnwire.NewSigFromECDSARawSignature(rawSignature) if err != nil { return hint, nil, err } // Finally, copy the serialized signature into the justice kit, // using the input's witness type to select the appropriate - // field. + // field switch inp.WitnessType() { case input.CommitmentRevoke: - copy(justiceKit.CommitToLocalSig[:], signature[:]) + justiceKit.CommitToLocalSig = signature case input.CommitSpendNoDelayTweakless: fallthrough case input.CommitmentNoDelay: fallthrough case input.CommitmentToRemoteConfirmed: - copy(justiceKit.CommitToRemoteSig[:], signature[:]) + justiceKit.CommitToRemoteSig = signature default: return hint, nil, fmt.Errorf("invalid witness type: %v", inp.WitnessType()) diff --git a/zpay32/decode.go b/zpay32/decode.go index b881d58694e..d37a34cf9db 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -91,8 +91,10 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { if err != nil { return nil, err } - var sig lnwire.Sig - copy(sig[:], sigBase256[:64]) + sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64]) + if err != nil { + return nil, err + } recoveryID := sigBase256[64] // The signature is over the hrp + the data the invoice, encoded in @@ -121,7 +123,7 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { } } else { headerByte := recoveryID + 27 + 4 - compactSign := append([]byte{headerByte}, sig[:]...) + compactSign := append([]byte{headerByte}, sig.RawBytes()...) pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash) if err != nil { return nil, err diff --git a/zpay32/encode.go b/zpay32/encode.go index a30d0c3911d..f8e1795e154 100644 --- a/zpay32/encode.go +++ b/zpay32/encode.go @@ -91,8 +91,10 @@ func (invoice *Invoice) Encode(signer MessageSigner) (string, error) { // From the header byte we can extract the recovery ID, and the last 64 // bytes encode the signature. recoveryID := sign[0] - 27 - 4 - var sig lnwire.Sig - copy(sig[:], sign[1:]) + sig, err := lnwire.NewSigFromWireECDSA(sign[1:]) + if err != nil { + return "", err + } // If the pubkey field was explicitly set, it must be set to the pubkey // used to create the signature. @@ -112,7 +114,10 @@ func (invoice *Invoice) Encode(signer MessageSigner) (string, error) { } // Convert the signature to base32 before writing it to the buffer. - signBase32, err := bech32.ConvertBits(append(sig[:], recoveryID), 8, 5, true) + signBase32, err := bech32.ConvertBits( + append(sig.RawBytes(), recoveryID), + 8, 5, true, + ) if err != nil { return "", err } From 10e27ac51b419740efe2091ec52f5e290329b2d9 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:34:21 -0800 Subject: [PATCH 49/87] lnwallet: update funding nonce gen to pass in priv key --- lnwallet/wallet.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index ab4af9e1d89..d866ab58845 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -1212,11 +1212,18 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation, // nonces: one for our local commitment, and one for their remote // commitment. if reservation.partialState.ChanType.IsTaproot() { - reservation.ourContribution.LocalNonce, err = musig2.GenNonces() + pubKeyOpt := musig2.WithPublicKey( + reservation.ourContribution.MultiSigKey.PubKey, + ) + reservation.ourContribution.LocalNonce, err = musig2.GenNonces( + pubKeyOpt, + ) if err != nil { return err } - reservation.ourContribution.RemoteNonce, err = musig2.GenNonces() + reservation.ourContribution.RemoteNonce, err = musig2.GenNonces( + pubKeyOpt, + ) if err != nil { return err } From cc184275662fb80757c2fe065f4fb1f438f740bc Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:35:11 -0800 Subject: [PATCH 50/87] lnwallet: parse incoming schnorr signature as "schnorr shell" In this context, a schnorr shell is packaging a 32 byte musig2 partial sig as only the latter 64 bytes of a fixed schnorr encoding. --- lnwallet/wallet.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index d866ab58845..12c9b229489 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -1560,7 +1560,7 @@ func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, "commitment: %w", err) } - sigTheirCommit = partialSig + sigTheirCommit = partialSig.ToSchnorrShell() // For regular channels, we can just send over a normal ECDSA signature // w/o any extra steps. @@ -1948,7 +1948,17 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, // actually be a wrapped musig2 signature, so we'll do a type // asset to the get the signature we actually need. switch partialSig := commitSig.(type) { - case *MusigPartialSig: + /*case *MusigPartialSig: + _, err := localSession.VerifyCommitSig( + commitTx, partialSig.sig, + ) + return err*/ + + // TODO(roasbeef): delete and go w/ sig in nonce, temp + case *schnorr.Signature: + remoteSig := new(MusigPartialSig) + remoteSig.FromSchnorrShell(partialSig) + _, err := localSession.VerifyCommitSig( commitTx, partialSig.sig, ) From 514254d6ff7fbf07151032e4bac3afd5c9ba4220 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:35:48 -0800 Subject: [PATCH 51/87] lnwallet: update ValidateChannel to recognize musig2+taproot chans --- lnwallet/wallet.go | 79 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 12c9b229489..41b1df4b3a7 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" @@ -1298,6 +1299,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, remoteCommitPoint, false, chanType, ourChanCfg, theirChanCfg, ) + // TODO(roasbeef): pass in taproot or not here, use to generate outputs ourCommitTx, err := CreateCommitTx( chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, theirChanCfg, localBalance, remoteBalance, 0, initiator, @@ -1431,6 +1433,8 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { theirContribution.MultiSigKey.PubKey, ) + // TODO(roasbeef): bind nonces as well? + // With our keys bound, we can now construct+sign the final // funding transaction and also obtain the chanPoint that // creates the channel. @@ -1960,7 +1964,7 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, remoteSig.FromSchnorrShell(partialSig) _, err := localSession.VerifyCommitSig( - commitTx, partialSig.sig, + commitTx, remoteSig.sig, ) return err @@ -2306,33 +2310,65 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, // First, we'll obtain a fully signed commitment transaction so we can // pass into it on the chanvalidate package for verification. + // + // TODO(roasbeef): need to pass in nonce state? or already should have + // own sig on disk channel, err := NewLightningChannel(l.Cfg.Signer, channelState, nil) if err != nil { return err } - signedCommitTx, err := channel.getSignedCommitTx() - if err != nil { - return err - } + + // TODO(roasbeef): need to update getSignedCommitTx for taproot, need + // to go w/ the deterministic nonces so can sign own version + + localKey := channelState.LocalChanCfg.MultiSigKey.PubKey + remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey // We'll also need the multi-sig witness script itself so the // chanvalidate package can check it for correctness against the // funding transaction, and also commitment validity. - localKey := channelState.LocalChanCfg.MultiSigKey.PubKey - remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey - witnessScript, err := input.GenMultiSigScript( - localKey.SerializeCompressed(), - remoteKey.SerializeCompressed(), + var ( + fundingScript []byte + commitCtx *chanvalidate.CommitmentContext ) - if err != nil { - return err - } - pkScript, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return err - } + if channelState.ChanType.IsTaproot() { + walletLog.Debugf("validating taproot channel: ChannelPoint(%v)", + channelState.FundingOutpoint) + + fundingScript, _, err = input.GenTaprootFundingScript( + localKey, remoteKey, int64(channel.Capacity), + ) + if err != nil { + return err + } + + // TODO(roasbeef): remove temp + commitCtx = nil + } else { + walletLog.Debugf("validating p2wsh channel: ChannelPoint(%v)", + channelState.FundingOutpoint) + + witnessScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return err + } + fundingScript, err = input.WitnessScriptHash(witnessScript) + if err != nil { + return err + } - // TODO(roasbeef): update to understand taproot + signedCommitTx, err := channel.getSignedCommitTx() + if err != nil { + return err + } + commitCtx = &chanvalidate.CommitmentContext{ + Value: channel.Capacity, + FullySignedCommitTx: signedCommitTx, + } + } // Finally, we'll pass in all the necessary context needed to fully // validate that this channel is indeed what we expect, and can be @@ -2341,12 +2377,9 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, Locator: &chanvalidate.OutPointChanLocator{ ChanPoint: channelState.FundingOutpoint, }, - MultiSigPkScript: pkScript, + MultiSigPkScript: fundingScript, FundingTx: fundingTx, - CommitCtx: &chanvalidate.CommitmentContext{ - Value: channel.Capacity, - FullySignedCommitTx: signedCommitTx, - }, + CommitCtx: commitCtx, }) if err != nil { return err From 038954bcc13dd646620229f506a8dc998a00ae74 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:36:10 -0800 Subject: [PATCH 52/87] lnwallet: update CreateHtlcTimeoutTx+CreateHtlcSuccessTx for musig2+taproot chans --- lnwallet/transactions.go | 103 +++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index 7fccf97c0d0..200895252a3 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/input" ) const ( @@ -41,9 +42,13 @@ var ( // state transition to create another output which actually allows redemption // or revocation of an HTLC. // -// In order to spend the HTLC output, the witness for the passed transaction -// should be: -// * <0> +// In order to spend the segwit v0 HTLC output, the witness for the passed +// transaction should be: +// - <0> +// +// In order to spend the segwit v1 (tapoot) HTLC output, the witness for the +// passed transaction should be: +// - func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay, leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) ( @@ -62,22 +67,43 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, } successTx.AddTxIn(txin) - // Next, we'll generate the script used as the output for all second - // level HTLC which forces a covenant w.r.t what can be done with all - // HTLC outputs. - script, err := SecondLevelHtlcScript( - chanType, initiator, revocationKey, delayKey, csvDelay, - leaseExpiry, - ) - if err != nil { - return nil, err + var pkScript []byte + + // Depending on if this is a taproot channel or not, we'll create a v0 + // vs v1 segwit script. + if chanType.IsTaproot() { + taprootOutputKey, err := input.TaprootSecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + pkScript, err = input.PayToTaprootScript(taprootOutputKey) + if err != nil { + return nil, err + } + } else { + + // Next, we'll generate the script used as the output for all second + // level HTLC which forces a covenant w.r.t what can be done with all + // HTLC outputs. + scriptInfo, err := SecondLevelHtlcScript( + chanType, initiator, revocationKey, delayKey, csvDelay, + leaseExpiry, + ) + if err != nil { + return nil, err + } + + pkScript = scriptInfo.PkScript } // Finally, the output is simply the amount of the HTLC (minus the // required fees), paying to the timeout script. successTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: script.PkScript, + PkScript: pkScript, }) return successTx, nil @@ -92,9 +118,13 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, // transaction is locked with an absolute lock-time so the sender can only // attempt to claim the output using it after the lock time has passed. // -// In order to spend the HTLC output, the witness for the passed transaction -// should be: -// * <0> <0> +// In order to spend the HTLC output for segwit v0, the witness for the passed +// transaction should be: +// - <0> <0> +// +// In order to spend the HTLC output for segwit v1, then witness for the passed +// transaction should be: +// - // // NOTE: The passed amount for the HTLC should take into account the required // fee rate at the time the HTLC was created. The fee should be able to @@ -121,22 +151,43 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, } timeoutTx.AddTxIn(txin) - // Next, we'll generate the script used as the output for all second - // level HTLC which forces a covenant w.r.t what can be done with all - // HTLC outputs. - script, err := SecondLevelHtlcScript( - chanType, initiator, revocationKey, delayKey, csvDelay, - leaseExpiry, - ) - if err != nil { - return nil, err + var pkScript []byte + + // Depending on if this is a taproot channel or not, we'll create a v0 + // vs v1 segwit script. + if chanType.IsTaproot() { + taprootOutputKey, err := input.TaprootSecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + pkScript, err = input.PayToTaprootScript(taprootOutputKey) + if err != nil { + return nil, err + } + + } else { + // Next, we'll generate the script used as the output for all second + // level HTLC which forces a covenant w.r.t what can be done with all + // HTLC outputs. + scriptInfo, err := SecondLevelHtlcScript( + chanType, initiator, revocationKey, delayKey, csvDelay, + leaseExpiry, + ) + if err != nil { + return nil, err + } + + pkScript = scriptInfo.PkScript } // Finally, the output is simply the amount of the HTLC (minus the // required fees), paying to the regular second level HTLC script. timeoutTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: script.PkScript, + PkScript: pkScript, }) return timeoutTx, nil From 8466ae0ed679c0b86f7861e2012a297f20b9f3e5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:36:32 -0800 Subject: [PATCH 53/87] lnwallet/btcwallet/signer: fix SignOutputRaw bug w/ non-default sighash --- lnwallet/btcwallet/signer.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index e620458055e..1a750ea2fad 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -1,13 +1,11 @@ package btcwallet import ( - "crypto/sha256" "fmt" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -403,7 +401,13 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, } } - sig, err := schnorr.ParseSignature(rawSig) + // The signature returned above might have a sighash flag + // attached if a non-default type was used. We'll slice this + // off if it exists to ensure we can properly parse the raw + // signature. + sig, err := schnorr.ParseSignature( + rawSig[:schnorr.SignatureSize], + ) if err != nil { return nil, err } From 13fea73d29c18e9908cd3148b9d269fa88f6ab09 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:36:57 -0800 Subject: [PATCH 54/87] lnwallet: properly pass in remoteCommit into NewMusigSession --- lnwallet/musig2_session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index b659ff1f9d1..a96f7dbb7cf 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -176,7 +176,7 @@ func NewMusigSession(noncePair MusigNoncePair, inputTxOut: inputTxOut, signerKeys: signerKeys, signer: signer, - remoteCommit: true, + remoteCommit: remoteCommit, }, nil } From 950b83ea42012d711b1bffc1ce8dbab0f0273fac Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:37:20 -0800 Subject: [PATCH 55/87] lnwallet: add temp methods for going to/from musig2 schnorr shell sigs --- lnwallet/musig2_session.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index a96f7dbb7cf..731e1ddf0dc 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -62,9 +62,33 @@ func (p *MusigPartialSig) Serialize() []byte { nonceX.PutBytesUnchecked(rawSig[:]) p.sig.S.PutBytesUnchecked(rawSig[32:]) + // TODO(roasbeef): update to 98 byte serialization? + return rawSig[:] } +// ToSchnorrShell... +func (p *MusigPartialSig) ToSchnorrShell() *schnorr.Signature { + var zeroVal btcec.FieldVal + return schnorr.NewSignature(&zeroVal, p.sig.S) +} + +// FromSchnorrShell... +// +// TODO(roasbeef): remove this and the above, pkg w/ nonce instead +func (p *MusigPartialSig) FromSchnorrShell(sig *schnorr.Signature) { + var ( + partialS btcec.ModNScalar + partialSBytes [32]byte + ) + copy(partialSBytes[:], sig.Serialize()[32:]) + partialS.SetBytes(&partialSBytes) + + p.sig = &musig2.PartialSignature{ + S: &partialS, + } +} + // TODO(roasbeef): parse method, can recompute the nonce like above? // Verify... @@ -92,6 +116,13 @@ type MusigNoncePair struct { RemoteNonce *musig2.Nonces } +// String... +func (n *MusigNoncePair) String() string { + return fmt.Sprintf("NoncePair(verification_nonce=%x, "+ + "signing_nonce=%x)", n.LocalNonce.PubNonce[:], + n.RemoteNonce.PubNonce[:]) +} + // MusigSession... type MusigSession struct { session *input.MuSig2SessionInfo From d28fe84e2e9e2d2ad00154340c43fba94094e5d8 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:38:02 -0800 Subject: [PATCH 56/87] lnwallet: pass in pubkey for nonce gen in musig2.SignCommit --- lnwallet/musig2_session.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 731e1ddf0dc..6ea78b2a402 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -129,8 +129,12 @@ type MusigSession struct { combinedNonce [musig2.PubNonceSize]byte + // nonces is the set of nonces that'll be used to generate/verify the + // next commitment. nonces MusigNoncePair + // nextNonces is the next set of nonces to start using once a + // revocation or new state occurs. nextNonces *MusigNoncePair // inputTxOut... @@ -148,6 +152,7 @@ type MusigSession struct { // signer... signer input.MuSig2Signer + // remoteCommit tracks if this session is for the remote commitment. remoteCommit bool } @@ -229,7 +234,9 @@ func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte, // SignCommit signs the passed commitment w/ the current signing (relative // remote) nonce. Given nonces should only ever be used once, once the method // returns a new nonce is returned, w/ the existing nonce blanked out. -func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.PubNonceSize]byte, error) { +func (m *MusigSession) SignCommit(tx *wire.MsgTx, +) (*MusigPartialSig, *[musig2.PubNonceSize]byte, error) { + // Before we can sign, we'll need to generate the sighash for their // commitment transaction. sigHash, err := taprootKeyspendSighash( @@ -244,6 +251,9 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.Pu var sigHashMsg [32]byte copy(sigHashMsg[:], sigHash) + walletLog.Infof("Generating new musig2 sig for session=%x, nonces=%s", + m.session.SessionID[:], m.nonces.String()) + sig, err := m.signer.MuSig2Sign( m.session.SessionID, sigHashMsg, false, ) @@ -255,7 +265,9 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, *[musig2.Pu // another nonce for the _next_ commitment. This'll go in the set of // nonces for the next state, as we still need the remote party's // verification nonce (their relative local nonce). - nextSigningNonce, err := musig2.GenNonces() + nextSigningNonce, err := musig2.GenNonces( + musig2.WithPublicKey(m.localKey.PubKey), + ) if err != nil { return nil, nil, fmt.Errorf("unable to gen new nonce: %w", err) } From d44303916d4ce108d34da3dca0d2d6abcf9331d7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:38:21 -0800 Subject: [PATCH 57/87] lnwallet: modify MusigSession.Refresh to return new instance instead of mutate --- lnwallet/musig2_session.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 6ea78b2a402..2e31e0c93e4 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -290,12 +290,12 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx, } // Refresh... -func (m *MusigSession) Refresh(nextNonce [musig2.PubNonceSize]byte) error { +func (m *MusigSession) Refresh(nextNonce [musig2.PubNonceSize]byte) (*MusigSession, error) { // At this point we should have a next nonce, otherwise this operation // is undefined as we haven't yet used our current nonce. if m.nextNonces == nil { // TODO(roasbeef): proper error - return fmt.Errorf("no next nonce") + return nil, fmt.Errorf("no next nonce") } // Now that we know we have the nonce we need, we can complete the @@ -316,18 +316,13 @@ func (m *MusigSession) Refresh(nextNonce [musig2.PubNonceSize]byte) error { // // TODO(roasbeef): can't actually clean up here? but need the stateless // signer thing? - defer m.signer.MuSig2Cleanup(m.session.SessionID) + oldSessionID := m.session.SessionID + defer m.signer.MuSig2Cleanup(oldSessionID) - var err error - m, err = NewMusigSession( + return NewMusigSession( *m.nextNonces, m.localKey, m.remoteKey, m.signer, m.inputTxOut, m.remoteCommit, ) - if err != nil { - return err - } - - return nil } // VerificationNonce... From 391c176a5409a870f47038a63e17958aa6811205 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:38:44 -0800 Subject: [PATCH 58/87] lnwallet; update musig2.VerifyCommitSig to pubkey nonce and set proper nonce --- lnwallet/musig2_session.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 2e31e0c93e4..27613477af9 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -365,6 +365,9 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, return nil, err } + walletLog.Infof("Verfiying new musig2 sig for session=%x, nonce=%s", + m.session.SessionID[:], m.nonces.String()) + if !partialSig.Verify(sigHash, m.remoteKey.PubKey) { return nil, fmt.Errorf("invalid partial commit sig") } @@ -374,13 +377,15 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, // new state transition. // // TODO(roasbeef): do this conditionally? - nextVerificationNonce, err := musig2.GenNonces() + nextVerificationNonce, err := musig2.GenNonces( + musig2.WithPublicKey(m.localKey.PubKey), + ) if err != nil { return nil, fmt.Errorf("unable to gen new nonce: %w", err) } m.nextNonces = &MusigNoncePair{ - RemoteNonce: nextVerificationNonce, + LocalNonce: nextVerificationNonce, } return &nextVerificationNonce.PubNonce, nil From d1eb662f3cfbc5a59f4c8e98dbc1c72e57da69c1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:38:55 -0800 Subject: [PATCH 59/87] lnwallet: add MusigSession.CombineSigs method --- lnwallet/musig2_session.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 27613477af9..0189d3e751f 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -391,6 +391,21 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, return &nextVerificationNonce.PubNonce, nil } +// CombineSigs... +func (m *MusigSession) CombineSigs(sigs ...*musig2.PartialSignature, +) (*schnorr.Signature, error) { + + sig, _, err := m.signer.MuSig2CombineSig( + m.session.SessionID, + sigs, + ) + if err != nil { + return nil, err + } + + return sig, nil +} + // MusigSessionCfg... type MusigSessionCfg struct { // LocalKey... From 0689779d24e235721b1674ab8133da282e7eb6af Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:40:21 -0800 Subject: [PATCH 60/87] lnwallet: use input.Signature rather than raw sig pointers This'll allow us to handle schnorr signatures and ECDSA signatures while only worrying about how to encode/decode them as the end, given a set of sharted interface methods. --- lnwallet/channel.go | 2 +- lnwallet/sigpool.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 04c72fb9f23..8ee4a8825de 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -316,7 +316,7 @@ type PaymentDescriptor struct { // local node. This signature is generated by the remote node and // stored by the local node in the case that local node needs to // broadcast their commitment transaction. - sig *ecdsa.Signature + sig input.Signature // addCommitHeight[Remote|Local] encodes the height of the commitment // which included this HTLC on either the remote or local commitment diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index 30dc37ae901..e077e621b36 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -36,7 +36,7 @@ type VerifyJob struct { // Sig is the raw signature generated using the above public key. This // is the signature to be verified. - Sig *ecdsa.Signature + Sig input.Signature // SigHash is a function closure generates the sighashes that the // passed signature is known to have signed. From 9c4ff679c17aad3b00b318d16b8a32bece2ca496 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:40:52 -0800 Subject: [PATCH 61/87] contractcourt: update the chainWatcher to recognize taproot+musig2 funding scripts --- contractcourt/chain_watcher.go | 42 ++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 3939fef0bb4..8af4db90ba2 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -287,17 +287,32 @@ func (c *chainWatcher) Start() error { } } - localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() - remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() - multiSigScript, err := input.GenMultiSigScript( - localKey, remoteKey, + localKey := chanState.LocalChanCfg.MultiSigKey.PubKey + remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey + + var ( + pkScript []byte + err error ) - if err != nil { - return err - } - pkScript, err := input.WitnessScriptHash(multiSigScript) - if err != nil { - return err + if chanState.ChanType.IsTaproot() { + pkScript, _, err = input.GenTaprootFundingScript( + localKey, remoteKey, 0, + ) + if err != nil { + return err + } + } else { + multiSigScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return err + } + pkScript, err = input.WitnessScriptHash(multiSigScript) + if err != nil { + return err + } } spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn( @@ -404,7 +419,7 @@ func (c *chainWatcher) handleUnknownLocalState( } remoteScript, _, err := lnwallet.CommitScriptToRemote( c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, - commitKeyRing.ToRemoteKey, leaseExpiry, + commitKeyRing.ToRemoteKey, leaseExpiry, nil, ) if err != nil { return false, err @@ -833,8 +848,11 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, } // Create an AnchorResolution for the breached state. + // + // TODO(roasbeef): make keyring for taproot chans to pass in instead of + // nil anchorRes, err := lnwallet.NewAnchorResolution( - c.cfg.chanState, commitSpend.SpendingTx, + c.cfg.chanState, commitSpend.SpendingTx, nil, ) if err != nil { return false, fmt.Errorf("unable to create anchor "+ From ef8ca2d87d5c4beac54933fd37adee30faf25698 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:41:39 -0800 Subject: [PATCH 62/87] funding: send nonces along side open+accept channel --- funding/manager.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index fe974c18227..00f8caf584e 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -1646,6 +1646,12 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, }, }, UpfrontShutdown: msg.UpfrontShutdownScript, + LocalNonce: &musig2.Nonces{ + PubNonce: msg.LocalNonce.Musig2Nonce, + }, + RemoteNonce: &musig2.Nonces{ + PubNonce: msg.RemoteNonce.Musig2Nonce, + }, } err = reservation.ProcessSingleContribution(remoteContribution) if err != nil { @@ -1665,7 +1671,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, localNonce *lnwire.LocalMusig2Nonce remoteNonce *lnwire.RemoteMusig2Nonce ) - if commitType == lnwallet.CommitmentTypeSimpleTaproot { + if commitType.IsTaproot() { localNonce = &lnwire.LocalMusig2Nonce{ Musig2Nonce: ourContribution.LocalNonce.PubNonce, } @@ -1676,7 +1682,6 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, // With the initiator's contribution recorded, respond with our // contribution in the next message of the workflow. - ourContribution := reservation.OurContribution() fundingAccept := lnwire.AcceptChannel{ PendingChannelID: msg.PendingChannelID, DustLimit: ourContribution.DustLimit, @@ -1884,6 +1889,12 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer, }, }, UpfrontShutdown: msg.UpfrontShutdownScript, + LocalNonce: &musig2.Nonces{ + PubNonce: msg.LocalNonce.Musig2Nonce, + }, + RemoteNonce: &musig2.Nonces{ + PubNonce: msg.RemoteNonce.Musig2Nonce, + }, } err = resCtx.reservation.ProcessContribution(remoteContribution) @@ -2059,6 +2070,8 @@ func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx, log.Infof("Generated ChannelPoint(%v) for pending_id(%x)", outPoint, pendingChanID[:]) + // TODO(roasbeef): convert signature for taproot stuff + var err error fundingCreated := &lnwire.FundingCreated{ PendingChannelID: pendingChanID, @@ -2103,6 +2116,14 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer, log.Infof("completing pending_id(%x) with ChannelPoint(%v)", pendingChanID[:], fundingOut) + // If this is a taproot channel, then we'll need to force the + // schnorr encoding. + // + // TODO(roasbeef): remove after nonce ting + if resCtx.reservation.IsTaproot() { + msg.CommitSig.ForceSchnorr() + } + commitSig, err := msg.CommitSig.ToSignature() if err != nil { log.Errorf("unable to parse signature: %v", err) @@ -2272,6 +2293,14 @@ func (f *Manager) handleFundingSigned(peer lnpeer.Peer, f.localDiscoverySignals[permChanID] = make(chan struct{}) f.localDiscoveryMtx.Unlock() + // If this is a taproot channel, then we'll need to force the + // schnorr encoding. + // + // TODO(roasbeef): remove after nonce ting + if resCtx.reservation.IsTaproot() { + msg.CommitSig.ForceSchnorr() + } + // The remote peer has responded with a signature for our commitment // transaction. We'll verify the signature for validity, then commit // the state to disk as we can now open the channel. From 76e49f570dde9b8b5f3fb905edb2235762a77866 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:41:55 -0800 Subject: [PATCH 63/87] funding: update makeFundingScript for musig2 chans --- funding/manager.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 00f8caf584e..80ee3820247 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -2533,15 +2533,29 @@ func (f *Manager) waitForFundingWithTimeout( // makeFundingScript re-creates the funding script for the funding transaction // of the target channel. func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { - localKey := channel.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() - remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + localKey := channel.LocalChanCfg.MultiSigKey.PubKey + remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey - multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) - if err != nil { - return nil, err - } + if channel.ChanType.IsTaproot() { + pkScript, _, err := input.GenTaprootFundingScript( + localKey, remoteKey, int64(channel.Capacity), + ) + if err != nil { + return nil, err + } - return input.WitnessScriptHash(multiSigScript) + return pkScript, nil + } else { + multiSigScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return nil, err + } + + return input.WitnessScriptHash(multiSigScript) + } } // waitForFundingConfirmation handles the final stages of the channel funding From c9d0f42136b527feae3a764468866b2fa1618796 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:43:15 -0800 Subject: [PATCH 64/87] funding: send musig2 nonces w/ funding_locked, add recv processing logic --- funding/manager.go | 115 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 8 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 80ee3820247..d0125584411 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -502,6 +503,17 @@ type Manager struct { nonceMtx sync.RWMutex chanIDNonce uint64 + // pendingMusigNonces is used to store the musig2 nonce we generate to + // send funding locked until we receive a funding locked message from + // the remote party. We'll use this to keep track of the nonce we + // generated, so we send the local+remote nonces to the peer state + // machine. + // + // NOTE: This map is protected by the nonceMtx above. + // + // TODO(roasbeef): replace w/ generic concurrent map + pendingMusigNonces map[lnwire.ChannelID]*lnwallet.MusigNoncePair + // activeReservations is a map which houses the state of all pending // funding workflows. activeReservations map[serializedPubKey]pendingChannels @@ -591,6 +603,7 @@ func NewFundingManager(cfg Config) (*Manager, error) { fundingRequests: make(chan *InitFundingMsg, msgBufferSize), localDiscoverySignals: make(map[lnwire.ChannelID]chan struct{}), handleFundingLockedBarriers: make(map[lnwire.ChannelID]struct{}), + pendingMusigNonces: make(map[lnwire.ChannelID]*lnwallet.MusigNoncePair), quit: make(chan struct{}), }, nil } @@ -2848,6 +2861,41 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel, } fundingLockedMsg := lnwire.NewFundingLocked(chanID, nextRevocation) + // If this is a taproot channel, then we also need to send along our + // set of musig2 nonces as well. + if completeChan.ChanType.IsTaproot() { + log.Infof("ChanID(%v): generating musig2 nonces...", + chanID) + + f.nonceMtx.Lock() + localNoncePair, ok := f.pendingMusigNonces[chanID] + if !ok { + // If we don't have any nonces generated yet for this + // first state, then we'll generate them now and stow + // them away. When we receive the funding locked + // message, we'll then pass along this same set of + // nonces. + newNonces, err := channel.GenMusigNonces() + if err != nil { + f.nonceMtx.Unlock() + return err + } + + // Now that we've generated the nonce for this channel, + // we'll store it in the set of pending nonces. + localNoncePair = newNonces + f.pendingMusigNonces[chanID] = localNoncePair + } + f.nonceMtx.Unlock() + + fundingLockedMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: localNoncePair.LocalNonce.PubNonce, + } + fundingLockedMsg.RemoteNonce = &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: localNoncePair.RemoteNonce.PubNonce, + } + } + // If the channel negotiated the option-scid-alias feature bit, we'll // send a TLV segment that includes an alias the peer can use in their // invoice hop hints. We'll send the first alias we find for the @@ -3037,6 +3085,7 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel, &completeChan.LocalChanCfg.MultiSigKey, completeChan.RemoteChanCfg.MultiSigKey.PubKey, *shortChanID, chanID, fwdMinHTLC, fwdMaxHTLC, ourPolicy, + completeChan.ChanType, ) if err != nil { return fmt.Errorf("error generating channel "+ @@ -3237,7 +3286,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel, f.cfg.IDKey, completeChan.IdentityPub, &completeChan.LocalChanCfg.MultiSigKey, completeChan.RemoteChanCfg.MultiSigKey.PubKey, - *shortChanID, chanID, + *shortChanID, chanID, completeChan.ChanType, ) if err != nil { return fmt.Errorf("channel announcement failed: %v", err) @@ -3507,6 +3556,52 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, return } + // If this is a taproot channel, then we'll need to map the received + // nonces to a nonce pair, and also fetch our pending nonces, which are + // required in order to make the channel whole. + var chanOpts []lnwallet.ChannelOpt + if channel.ChanType.IsTaproot() { + f.nonceMtx.Lock() + localNonces, ok := f.pendingMusigNonces[chanID] + if !ok { + // If there's no pending nonce for this channel ID, + // then we'll generate one now. + noncePair, err := lnwallet.NewMusigChannelNonces( + channel.LocalChanCfg.MultiSigKey.PubKey, + ) + if err != nil { + f.nonceMtx.Unlock() + log.Error("unable to generate musig channel "+ + "nonces: %v", err) + return + } + + localNonces = noncePair + f.pendingMusigNonces[chanID] = localNonces + } + f.nonceMtx.Unlock() + + remoteNonces := &lnwallet.MusigNoncePair{ + LocalNonce: &musig2.Nonces{ + PubNonce: msg.LocalNonce.Musig2Nonce, + }, + RemoteNonce: &musig2.Nonces{ + PubNonce: msg.RemoteNonce.Musig2Nonce, + }, + } + + log.Infof("ChanID(%v): applying local+remote musig2 nonces", + chanID) + + chanOpts = append( + chanOpts, + lnwallet.WithLocalMusigNonces(localNonces), + lnwallet.WithRemoteMusigNonces(remoteNonces), + ) + + // TODO(roasbeef): case of taproot chan w/ nonces not sent + } + // Launch a defer so we _ensure_ that the channel barrier is properly // closed even if the target peer is no longer online at this point. defer func() { @@ -3524,7 +3619,11 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, f.barrierMtx.Unlock() }() - if err := peer.AddNewChannel(channel, f.quit); err != nil { + err = peer.AddNewChannel(&lnpeer.NewChannel{ + OpenChannel: channel, + ChanOpts: chanOpts, + }, f.quit) + if err != nil { log.Errorf("Unable to add new channel %v with peer %x: %v", channel.FundingOutpoint, peer.IdentityKey().SerializeCompressed(), err, @@ -3551,9 +3650,9 @@ type chanAnnouncement struct { func (f *Manager) newChanAnnouncement(localPubKey, remotePubKey *btcec.PublicKey, localFundingKey *keychain.KeyDescriptor, remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID, - chanID lnwire.ChannelID, fwdMinHTLC, - fwdMaxHTLC lnwire.MilliSatoshi, - ourPolicy *channeldb.ChannelEdgePolicy) (*chanAnnouncement, error) { + chanID lnwire.ChannelID, fwdMinHTLC, fwdMaxHTLC lnwire.MilliSatoshi, + ourPolicy *channeldb.ChannelEdgePolicy, + chanType channeldb.ChannelType) (*chanAnnouncement, error) { chainHash := *f.cfg.Wallet.Cfg.NetParams.GenesisHash @@ -3712,7 +3811,7 @@ func (f *Manager) newChanAnnouncement(localPubKey, func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, localFundingKey *keychain.KeyDescriptor, remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID, - chanID lnwire.ChannelID) error { + chanID lnwire.ChannelID, chanType channeldb.ChannelType) error { // First, we'll create the batch of announcements to be sent upon // initial channel creation. This includes the channel announcement @@ -3723,7 +3822,7 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, // only use the channel announcement message from the returned struct. ann, err := f.newChanAnnouncement(localIDKey, remoteIDKey, localFundingKey, remoteFundingKey, shortChanID, chanID, - 0, 0, nil, + 0, 0, nil, chanType, ) if err != nil { log.Errorf("can't generate channel announcement: %v", err) @@ -4101,7 +4200,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { localNonce *lnwire.LocalMusig2Nonce remoteNonce *lnwire.RemoteMusig2Nonce ) - if commitType == lnwallet.CommitmentTypeSimpleTaproot { + if commitType.IsTaproot() { localNonce = &lnwire.LocalMusig2Nonce{ Musig2Nonce: ourContribution.LocalNonce.PubNonce, } From 49bd2a4f2d30ab8b9ff02390744c0170687a8471 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:43:27 -0800 Subject: [PATCH 65/87] funding: reject funding attempts for public taproot channels --- funding/manager.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index d0125584411..b68d53eaef3 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -1431,15 +1431,24 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, } } + public := msg.ChannelFlags&lnwire.FFAnnounceChannel != 0 + switch { // Sending the option-scid-alias channel type for a public channel is // disallowed. - public := msg.ChannelFlags&lnwire.FFAnnounceChannel != 0 - if public && scid { + case public && scid: err = fmt.Errorf("option-scid-alias chantype for public " + "channel") log.Error(err) f.failFundingFlow(peer, msg.PendingChannelID, err) return + + // The current variant of taproot channels can only be should only be + // used with unadvertised channels for now. + case commitType == lnwallet.CommitmentTypeSimpleTaproot && public: + err = fmt.Errorf("taproot channel type for public channel") + log.Error(err) + f.failFundingFlow(peer, msg.PendingChannelID, err) + return } req := &lnwallet.InitFundingReserveMsg{ From 9463eea1a7cd23a9115cd0327a1a0d471c2a1ec7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:44:39 -0800 Subject: [PATCH 66/87] funding: add the taproot feature bit for funding announcements We add this here as although these channels aren't yet be publicly verified, we need to be able to differentiate segwit v0 vs v1 channels in the router, as we'll still attempt to ensure that the specified channel exists on chain. --- funding/manager.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/funding/manager.go b/funding/manager.go index b68d53eaef3..d04551dadbd 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -3674,6 +3674,18 @@ func (f *Manager) newChanAnnouncement(localPubKey, ChainHash: chainHash, } + // If this is a taproot channel, then we'll set a special bit in the + // feature vector to indicate to the routing layer that this needs a + // slightly different type of validation. + // + // TODO(roasbeef): temp, remove after gossip 1.5 + if chanType.IsTaproot() { + log.Debugf("Applying taproot feature bit to "+ + "ChannelAnnouncement for %v", chanID) + + chanAnn.Features.Set(lnwire.SimpleTaprootChannelsRequired) + } + // The chanFlags field indicates which directed edge of the channel is // being updated within the ChannelUpdateAnnouncement announcement // below. A value of zero means it's the edge of the "first" node and 1 From 67e0535eb0ec00f1d862be857d5e1cfab74eb22c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:45:12 -0800 Subject: [PATCH 67/87] routing: update chan validate logic to recognize musig2 channels --- routing/router.go | 65 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/routing/router.go b/routing/router.go index 348914db733..3f1c0b78d2e 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1414,6 +1414,58 @@ func (r *ChannelRouter) addZombieEdge(chanID uint64) error { return nil } +// makeFundingScript... +// +// TODO(roasbeef: export and use elsewhere? +func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, + chanFeatures []byte) ([]byte, error) { + + // In order to make the correct funding script, we'll need to parse the + // chanFeatures bytes into a feature vector we can interact with. + rawFeatures := lnwire.NewRawFeatureVector() + err := rawFeatures.Decode(bytes.NewReader(chanFeatures)) + if err != nil { + return nil, fmt.Errorf("unable to parse chan feature "+ + "bits: %w", err) + } + + chanFeatureBits := lnwire.NewFeatureVector( + rawFeatures, lnwire.Features, + ) + if chanFeatureBits.HasFeature(lnwire.SimpleTaprootChannelsOptional) { + pubKey1, err := btcec.ParsePubKey(bitcoinKey1) + if err != nil { + return nil, err + } + pubKey2, err := btcec.ParsePubKey(bitcoinKey2) + if err != nil { + return nil, err + } + + fundingScript, _, err := input.GenTaprootFundingScript( + pubKey1, pubKey2, 0, + ) + if err != nil { + return nil, err + } + + return fundingScript, nil + } else { + witnessScript, err := input.GenMultiSigScript( + bitcoinKey1[:], bitcoinKey2[:], + ) + if err != nil { + return nil, err + } + pkScript, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return pkScript, nil + } +} + // processUpdate processes a new relate authenticated channel/edge, node or // channel/edge update network update. If the update didn't affect the internal // state of the draft due to either being out of date, invalid, or redundant, @@ -1520,16 +1572,13 @@ func (r *ChannelRouter) processUpdate(msg interface{}, // Recreate witness output to be sure that declared in channel // edge bitcoin keys and channel value corresponds to the // reality. - witnessScript, err := input.GenMultiSigScript( + fundingPkScript, err := makeFundingScript( msg.BitcoinKey1Bytes[:], msg.BitcoinKey2Bytes[:], + msg.Features, ) if err != nil { return err } - pkScript, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return err - } // Next we'll validate that this channel is actually well // formed. If this check fails, then this channel either @@ -1539,7 +1588,7 @@ func (r *ChannelRouter) processUpdate(msg interface{}, Locator: &chanvalidate.ShortChanIDChanLocator{ ID: channelID, }, - MultiSigPkScript: pkScript, + MultiSigPkScript: fundingPkScript, FundingTx: fundingTx, }) if err != nil { @@ -1556,10 +1605,6 @@ func (r *ChannelRouter) processUpdate(msg interface{}, // Now that we have the funding outpoint of the channel, ensure // that it hasn't yet been spent. If so, then this channel has // been closed so we'll ignore it. - fundingPkScript, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return err - } chanUtxo, err := r.cfg.Chain.GetUtxo( fundingPoint, fundingPkScript, channelID.BlockHeight, r.quit, From b4b81576b089db91552e0d5d8a9cbc16e5cfde08 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:45:31 -0800 Subject: [PATCH 68/87] lnwallet: add IsTaproot method to ChannelReservation --- lnwallet/reservation.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 32866bcd603..89f5f29671b 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -485,6 +485,14 @@ func (r *ChannelReservation) IsZeroConf() bool { return r.partialState.IsZeroConf() } +// IsTaproot... +func (r *ChannelReservation) IsTaproot() bool { + r.RLock() + defer r.RUnlock() + + return r.partialState.ChanType.IsTaproot() +} + // CommitConstraints takes the constraints that the remote party specifies for // the type of commitments that we can generate for them. These constraints // include several parameters that serve as flow control restricting the amount From 8aa81f8226550e43a86d46be958052ec44677022 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:46:02 -0800 Subject: [PATCH 69/87] lnwallet: update CommitScriptToRemote to properly set taproot script --- lnwallet/commitment.go | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 59a006c6fa7..7465c3db288 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -332,42 +332,42 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, WitnessScript: script, }, 1, nil - // If this channel type has anchors, we derive the delayed to_remote - // script. - case chanType.HasAnchors(): - script, err := input.CommitScriptToRemoteConfirmed(remoteKey) + // For taproot channels, we'll use a slightly different format, where + // the top-level key is the combined funding key (w/o the bip 86 + // tweak), with the sole tap leaf enforcing the 1 CSV delay. + case chanType.IsTaproot(): + toRemoteKey, err := input.TaprootCommitScriptToRemote( + combinedFundingKey, remoteKey, + ) if err != nil { return nil, 0, err } - p2wsh, err := input.WitnessScriptHash(script) + toRemotePkScript, err := input.PayToTaprootScript(toRemoteKey) if err != nil { return nil, 0, err } return &ScriptInfo{ - PkScript: p2wsh, - WitnessScript: script, + PkScript: toRemotePkScript, }, 1, nil - // For taproot channels, we'll use a slightly different format, where - // the top-level key is the combined funding key (w/o the bip 86 - // tweak), with the sole tap leaf enforcing the 1 CSV delay. - case chanType.IsTaproot(): - toRemoteKey, err := input.TaprootCommitScriptToRemote( - combinedFundingKey, remoteKey, - ) + // If this channel type has anchors, we derive the delayed to_remote + // script. + case chanType.HasAnchors(): + script, err := input.CommitScriptToRemoteConfirmed(remoteKey) if err != nil { return nil, 0, err } - toRemotePkScript, err := input.PayToTaprootScript(toRemoteKey) + p2wsh, err := input.WitnessScriptHash(script) if err != nil { return nil, 0, err } return &ScriptInfo{ - PkScript: toRemotePkScript, + PkScript: p2wsh, + WitnessScript: script, }, 1, nil default: @@ -506,9 +506,12 @@ func HtlcSuccessFee(chanType channeldb.ChannelType, return 0 } + // TODO(roasbeef): fee is still off here? + if chanType.HasAnchors() { return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed) } + return feePerKw.FeeForWeight(input.HtlcSuccessWeight) } From 1646d7c0023cc48ac4625db92d4c0e760e1ff692 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:46:32 -0800 Subject: [PATCH 70/87] lnwallet: update genHtlcScript to dispense taproot HTLC scripts --- lnwallet/commitment.go | 162 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 12 deletions(-) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 7465c3db288..04a615610f8 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -995,12 +995,10 @@ func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool, return ourBalance, theirBalance, nil } -// genHtlcScript generates the proper P2WSH public key scripts for the HTLC -// output modified by two-bits denoting if this is an incoming HTLC, and if the -// HTLC is being applied to their commitment transaction or ours. -func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, - timeout uint32, rHash [32]byte, - keyRing *CommitmentKeyRing) ([]byte, []byte, error) { +// genSegwitV0HtlcScript.... +func genSegwitV0HtlcScript(chanType channeldb.ChannelType, + isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) (*ScriptInfo, error) { var ( witnessScript []byte @@ -1054,17 +1052,157 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, ) } if err != nil { - return nil, nil, err + return nil, err } // Now that we have the redeem scripts, create the P2WSH public key // script for the output itself. htlcP2WSH, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: htlcP2WSH, + WitnessScript: witnessScript, + }, nil +} + +// genTaprootHtlcScript.... +func genTaprootHtlcScript(chanType channeldb.ChannelType, isIncoming, + ourCommit bool, timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) (*ScriptInfo, error) { + + var ( + taprootKey *btcec.PublicKey + secondLevelScript []byte + ) + + // Generate the proper redeem scripts for the HTLC output modified by + // two-bits denoting if this is an incoming HTLC, and if the HTLC is + // being applied to their commitment transaction or ours. + switch { + // The HTLC is paying to us, and being applied to our commitment + // transaction. So we need to use the receiver's version of HTLC the + // script. + case isIncoming && ourCommit: + scriptTree, err := input.ReceiverHTLCScriptTaproot( + timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // As this is an HTLC on our commitment transaction, the second + // level path we care about here is the success path. + // Therefore, we'll grab the tapLeaf corresponding to the + // success path. + secondLevelScript = scriptTree.SuccessTapLeaf.Script + + // We're being paid via an HTLC by the remote party, and the HTLC is + // being added to their commitment transaction, so we use the sender's + // version of the HTLC script. + case isIncoming && !ourCommit: + scriptTree, err := input.SenderHTLCScriptTaproot( + keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // In this case, this is an incoming HTLC on the commitment + // transaction of the remote party, so we'll return the timeout + // tapleaf since that's the second level spend they need in the + // case of a broadcast. + secondLevelScript = scriptTree.TimeoutTapLeaf.Script + + // We're sending an HTLC which is being added to our commitment + // transaction. Therefore, we need to use the sender's version of the + // HTLC script. + case !isIncoming && ourCommit: + scriptTree, err := input.SenderHTLCScriptTaproot( + keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // This is an outgoing HTLC on our commitment transaction, so + // we need to be able to generate/verify signatures for the + // timeout path. + secondLevelScript = scriptTree.TimeoutTapLeaf.Script + + // Finally, we're paying the remote party via an HTLC, which is being + // added to their commitment transaction. Therefore, we use the + // receiver's version of the HTLC script. + case !isIncoming && !ourCommit: + scriptTree, err := input.ReceiverHTLCScriptTaproot( + timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // This is an outgoing HTLC on the remote party's commitment + // transaction. In this case if they go on chain, they'll need + // the second level success spend, so we grab that tapscript + // path. + secondLevelScript = scriptTree.SuccessTapLeaf.Script + } + + // Now that we have the redeem scripts, create the P2TR public key + // script for the output itself. + p2trOutput, err := input.PayToTaprootScript(taprootKey) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: p2trOutput, + WitnessScript: secondLevelScript, + }, nil +} + +// genHtlcScript generates the proper P2WSH public key scripts for the HTLC +// output modified by two-bits denoting if this is an incoming HTLC, and if the +// HTLC is being applied to their commitment transaction or ours. +func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, + timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) ([]byte, []byte, error) { + + var ( + scriptInfo *ScriptInfo + err error + ) + + if !chanType.IsTaproot() { + scriptInfo, err = genSegwitV0HtlcScript( + chanType, isIncoming, ourCommit, timeout, rHash, + keyRing, + ) + } else { + scriptInfo, err = genTaprootHtlcScript( + chanType, isIncoming, ourCommit, timeout, rHash, + keyRing, + ) + } if err != nil { return nil, nil, err } - return htlcP2WSH, witnessScript, nil + return scriptInfo.PkScript, scriptInfo.WitnessScript, nil } // addHTLC adds a new HTLC to the passed commitment transaction. One of four @@ -1081,7 +1219,7 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool, timeout := paymentDesc.Timeout rHash := paymentDesc.RHash - p2wsh, witnessScript, err := genHtlcScript( + witnessProgram, witnessScript, err := genHtlcScript( chanType, isIncoming, ourCommit, timeout, rHash, keyRing, ) if err != nil { @@ -1090,15 +1228,15 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool, // Add the new HTLC outputs to the respective commitment transactions. amountPending := int64(paymentDesc.Amount.ToSatoshis()) - commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh)) + commitTx.AddTxOut(wire.NewTxOut(amountPending, witnessProgram)) // Store the pkScript of this particular PaymentDescriptor so we can // quickly locate it within the commitment transaction later. if ourCommit { - paymentDesc.ourPkScript = p2wsh + paymentDesc.ourPkScript = witnessProgram paymentDesc.ourWitnessScript = witnessScript } else { - paymentDesc.theirPkScript = p2wsh + paymentDesc.theirPkScript = witnessProgram paymentDesc.theirWitnessScript = witnessScript } From afd9e5c0f9000b05d49135a3126c508ca9e396fd Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:48:59 -0800 Subject: [PATCH 71/87] lnwallet: update NewLightningChannel with new musig2 opts This'll allow a new channel state machine to be created in contexts where the set of nonces is already known (funding locked + channel reest). --- lnwallet/channel.go | 89 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 8ee4a8825de..4a3fbae0cf7 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/txsort" @@ -25,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" ) @@ -1325,6 +1327,34 @@ type LightningChannel struct { sync.RWMutex } +// ChannelOpt... +type ChannelOpt func(*channelOpts) + +// WithLocalMusigNonces... +func WithLocalMusigNonces(nonces *MusigNoncePair) ChannelOpt { + return func(o *channelOpts) { + o.localNonces = nonces + } +} + +// WithRemoteMusigNonces... +func WithRemoteMusigNonces(nonces *MusigNoncePair) ChannelOpt { + return func(o *channelOpts) { + o.remoteNonces = nonces + } +} + +// channelOpts... +type channelOpts struct { + localNonces *MusigNoncePair + remoteNonces *MusigNoncePair +} + +// defaultChannelOpts... +func defaultChannelOpts() *channelOpts { + return &channelOpts{} +} + // NewLightningChannel creates a new, active payment channel given an // implementation of the chain notifier, channel database, and the current // settled channel state. Throughout state transitions, then channel will @@ -1332,7 +1362,12 @@ type LightningChannel struct { // manner. func NewLightningChannel(signer input.Signer, state *channeldb.OpenChannel, - sigPool *SigPool) (*LightningChannel, error) { + sigPool *SigPool, chanOpts ...ChannelOpt) (*LightningChannel, error) { + + opts := defaultChannelOpts() + for _, optFunc := range chanOpts { + optFunc(opts) + } localCommit := state.LocalCommitment remoteCommit := state.RemoteCommitment @@ -1365,6 +1400,18 @@ func NewLightningChannel(signer input.Signer, log: build.NewPrefixLog(logPrefix, walletLog), } + // At this point, we mwy already have of nonces that were passed in, so + // we'll check that now as this lets us skip some steps later. + if opts.localNonces != nil { + lc.nextNoncePair = opts.localNonces + } + if lc.nextNoncePair != nil && opts.remoteNonces != nil { + err := lc.InitRemoteMusigNonces(opts.remoteNonces) + if err != nil { + return nil, err + } + } + // With the main channel struct reconstructed, we'll now restore the // commitment state in memory and also the update logs themselves. err := lc.restoreCommitState(&localCommit, &remoteCommit) @@ -1385,20 +1432,38 @@ func NewLightningChannel(signer input.Signer, // createSignDesc derives the SignDescriptor for commitment transactions from // other fields on the LightningChannel. func (lc *LightningChannel) createSignDesc() error { - localKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - remoteKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) - if err != nil { - return err - } + var ( + fundingPkScript, multiSigScript []byte + err error + ) + chanState := lc.channelState + if chanState.ChanType.IsTaproot() { + fundingPkScript, _, err = input.GenTaprootFundingScript( + chanState.LocalChanCfg.MultiSigKey.PubKey, + chanState.RemoteChanCfg.MultiSigKey.PubKey, + int64(lc.channelState.Capacity), + ) + if err != nil { + return err + } + } else { + localKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + remoteKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() - fundingPkScript, err := input.WitnessScriptHash(multiSigScript) - if err != nil { - return err + multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) + if err != nil { + return err + } + + fundingPkScript, err = input.WitnessScriptHash(multiSigScript) + if err != nil { + return err + } } + lc.fundingOutput = wire.TxOut{ PkScript: fundingPkScript, Value: int64(lc.channelState.Capacity), From 3b27533323c98983562c1ac66ddbcad8b08d0c37 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:52:05 -0800 Subject: [PATCH 72/87] lnwallet: update sig pool to support signing+verifying 2nd level taproot scripts --- lnwallet/channel.go | 125 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 22 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 4a3fbae0cf7..701a67b7299 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3249,7 +3249,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // If the HTLC isn't dust, then we'll create an empty sign job // to add to the batch momentarily. - sigJob := SignJob{} + var sigJob SignJob sigJob.Cancel = cancelChan sigJob.Resp = make(chan SignJobResp, 1) @@ -3276,21 +3276,38 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, return nil, nil, err } + // Construct a full hash cache as we may be signing a segwit v1 + // sighash. + txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] + prevFetcher := txscript.NewCannedPrevOutputFetcher( + txOut.PkScript, int64(htlc.Amount.ToSatoshis()), + ) + hashCache := txscript.NewTxSigHashes(sigJob.Tx, prevFetcher) + // Finally, we'll generate a sign descriptor to generate a // signature to give to the remote party for this commitment // transaction. Note we use the raw HTLC amount. txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] sigJob.SignDesc = input.SignDescriptor{ - KeyDesc: localChanCfg.HtlcBasePoint, - SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlc.theirWitnessScript, - Output: txOut, - HashType: sigHashType, - SigHashes: input.NewTxSigHashesV0Only(sigJob.Tx), - InputIndex: 0, + KeyDesc: localChanCfg.HtlcBasePoint, + SingleTweak: keyRing.LocalHtlcKeyTweak, + WitnessScript: htlc.theirWitnessScript, + Output: txOut, + PrevOutputFetcher: prevFetcher, + HashType: sigHashType, + SigHashes: hashCache, + InputIndex: 0, } sigJob.OutputIndex = htlc.remoteOutputIndex + // If this is a taproot channel, then we'll need to set the + // method type to ensure we generate a valid signature. + if chanType.IsTaproot() { + sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod + } + + walletLog.Infof("sign desc second level: %v", spew.Sdump(sigJob.SignDesc)) + sigBatch = append(sigBatch, sigJob) } for _, htlc := range remoteCommitView.outgoingHTLCs { @@ -3330,21 +3347,35 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, return nil, nil, err } + // Construct a full hash cache as we may be signing a segwit v1 + // sighash. + txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] + prevFetcher := txscript.NewCannedPrevOutputFetcher( + txOut.PkScript, int64(htlc.Amount.ToSatoshis()), + ) + hashCache := txscript.NewTxSigHashes(sigJob.Tx, prevFetcher) + // Finally, we'll generate a sign descriptor to generate a // signature to give to the remote party for this commitment // transaction. Note we use the raw HTLC amount. - txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] sigJob.SignDesc = input.SignDescriptor{ - KeyDesc: localChanCfg.HtlcBasePoint, - SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlc.theirWitnessScript, - Output: txOut, - HashType: sigHashType, - SigHashes: input.NewTxSigHashesV0Only(sigJob.Tx), - InputIndex: 0, + KeyDesc: localChanCfg.HtlcBasePoint, + SingleTweak: keyRing.LocalHtlcKeyTweak, + WitnessScript: htlc.theirWitnessScript, + Output: txOut, + PrevOutputFetcher: prevFetcher, + HashType: sigHashType, + SigHashes: hashCache, + InputIndex: 0, } sigJob.OutputIndex = htlc.remoteOutputIndex + // If this is a taproot channel, then we'll need to set the + // method type to ensure we generate a valid signature. + if chanType.IsTaproot() { + sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod + } + sigBatch = append(sigBatch, sigJob) } @@ -3772,8 +3803,6 @@ type NewCommitState struct { // for the remote party's commitment are also returned. func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { - // TODO(roasbeef): make return val into struct w/ all the new args - lc.Lock() defer lc.Unlock() @@ -3854,8 +3883,6 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { }), ) - // TODO(roasbeef): update HLTC batch jobs - // With the commitment view constructed, if there are any HTLC's, we'll // need to generate signatures of each of them for the remote party's // commitment state. We do so in two phases: first we generate and @@ -4444,11 +4471,30 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, return nil, err } + htlcAmt := int64(htlc.Amount.ToSatoshis()) + + if chanType.IsTaproot() { + // TODO(roasbeef): add abstraction in front + prevFetcher := txscript.NewCannedPrevOutputFetcher( + htlc.ourPkScript, htlcAmt, + ) + hashCache := txscript.NewTxSigHashes( + successTx, prevFetcher, + ) + tapLeaf := txscript.NewBaseTapLeaf( + htlc.ourWitnessScript, + ) + return txscript.CalcTapscriptSignaturehash( + hashCache, sigHashType, successTx, 0, + prevFetcher, tapLeaf, + ) + } + hashCache := input.NewTxSigHashesV0Only(successTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, sigHashType, successTx, 0, - int64(htlc.Amount.ToSatoshis()), + htlcAmt, ) if err != nil { return nil, err @@ -4463,6 +4509,15 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, "signatures") } + if chanType.IsTaproot() { + // TODO(roasbeef): remove, temp hack + // * when sigs get encoded on the wire, they + // default back to sigTypeECDSA, so we need to + // force schnorr here to get the proper sig + // below for validation + htlcSigs[i].ForceSchnorr() + } + // With the sighash generated, we'll also store the // signature so it can be written to disk if this state // is valid. @@ -4470,6 +4525,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, if err != nil { return nil, err } + htlc.sig = sig // Otherwise, if this is an outgoing HTLC, then we'll need to @@ -4499,11 +4555,30 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, return nil, err } + htlcAmt := int64(htlc.Amount.ToSatoshis()) + + if chanType.IsTaproot() { + // TODO(roasbeef): add abstraction in front + prevFetcher := txscript.NewCannedPrevOutputFetcher( + htlc.ourPkScript, htlcAmt, + ) + hashCache := txscript.NewTxSigHashes( + timeoutTx, prevFetcher, + ) + tapLeaf := txscript.NewBaseTapLeaf( + htlc.ourWitnessScript, + ) + return txscript.CalcTapscriptSignaturehash( + hashCache, sigHashType, timeoutTx, 0, + prevFetcher, tapLeaf, + ) + } + hashCache := input.NewTxSigHashesV0Only(timeoutTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, sigHashType, timeoutTx, 0, - int64(htlc.Amount.ToSatoshis()), + htlcAmt, ) if err != nil { return nil, err @@ -4518,6 +4593,11 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, "signatures") } + if chanType.IsTaproot() { + // TODO(roasbeef): remove, temp hack + htlcSigs[i].ForceSchnorr() + } + // With the sighash generated, we'll also store the // signature so it can be written to disk if this state // is valid. @@ -4525,6 +4605,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, if err != nil { return nil, err } + htlc.sig = sig default: From 15f0565f5f76981ced9ee0a2b8b8f63b7a842e93 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:53:01 -0800 Subject: [PATCH 73/87] lnwallet: update channel state machine dance to support new musig2 flow --- lnwallet/channel.go | 179 +++++++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 60 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 701a67b7299..bfcdee191e8 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3904,10 +3904,12 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // While the jobs are being carried out, we'll Sign their version of // the new commitment transaction while we're waiting for the rest of // the HTLC signatures to be processed. - var nextSigningNonce lnwire.Musig2Nonce - if !lc.channelState.ChanType.IsTaproot() { - localSession := lc.musigSessions.LocalSession - partialSig, nextNonce, err := localSession.SignCommit( + // + // TODO(roasbeef): abstract into CommitSigner interface? + var nextSigningNonce *lnwire.Musig2Nonce + if lc.channelState.ChanType.IsTaproot() { + remoteSession := lc.musigSessions.RemoteSession + partialSig, nextNonce, err := remoteSession.SignCommit( newCommitView.txn, ) if err != nil { @@ -3915,9 +3917,15 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { return nil, err } - nextSigningNonce = *nextNonce + nextSigningNonce = (*lnwire.Musig2Nonce)(nextNonce) - copy(sig[:], partialSig.Serialize()) + // TODO(roasbeef): to just carry nonce w/? + sig, err = lnwire.NewSigFromSchnorrRawSignature( + partialSig.Serialize(), + ) + if err != nil { + return nil, err + } } else { lc.signDesc.SigHashes = input.NewTxSigHashesV0Only( newCommitView.txn, @@ -4014,6 +4022,8 @@ func (lc *LightningChannel) ProcessChanSyncMsg( msg *lnwire.ChannelReestablish) ([]lnwire.Message, []channeldb.CircuitKey, []channeldb.CircuitKey, error) { + // TODO(roasbeef): need to replace w/ received nonces + // Now we'll examine the state we have, vs what was contained in the // chain sync message. If we're de-synchronized, then we'll send a // batch of messages which when applied will kick start the chain @@ -4243,6 +4253,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // With the batch of updates accumulated, we'll now re-send the // original CommitSig message required to re-sync their remote // commitment chain with our local version of their chain. + // + // TODO(roasbeef): need to re-sign commitment states w/ + // fresh nonce commitUpdates = append(commitUpdates, commitDiff.CommitSig) // NOTE: If a revocation is not owed, then updates is empty. @@ -4437,7 +4450,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, var ( htlcIndex uint64 sigHash func() ([]byte, error) - sig *ecdsa.Signature + sig input.Signature err error ) @@ -4768,25 +4781,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { }), ) - // TODO(roasbeef): update verification below - - // Construct the sighash of the commitment transaction corresponding to - // this newly proposed state update. - localCommitTx := localCommitmentView.txn - multiSigScript := lc.signDesc.WitnessScript - hashCache := input.NewTxSigHashesV0Only(localCommitTx) - sigHash, err := txscript.CalcWitnessSigHash( - multiSigScript, hashCache, txscript.SigHashAll, - localCommitTx, 0, int64(lc.channelState.Capacity), - ) - if err != nil { - // TODO(roasbeef): fetchview has already mutated the HTLCs... - // * need to either roll-back, or make pure - return err - } - // As an optimization, we'll generate a series of jobs for the worker - // pool to verify each of the HTLc signatures presented. Once + // pool to verify each of the HTLC signatures presented. Once // generated, we'll submit these jobs to the worker pool. var leaseExpiry uint32 if lc.channelState.ChanType.HasLeaseExpiration() { @@ -4805,31 +4801,78 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { cancelChan := make(chan struct{}) verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan) + localCommitTx := localCommitmentView.txn + multiSigScript := lc.signDesc.WitnessScript + prevFetcher := txscript.NewCannedPrevOutputFetcher( + multiSigScript, int64(lc.channelState.Capacity), + ) + hashCache := txscript.NewTxSigHashes(localCommitTx, prevFetcher) + // While the HTLC verification jobs are proceeding asynchronously, // we'll ensure that the newly constructed commitment state has a valid // signature. - verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey + // + // To do that we'll, construct the sighash of the commitment + // transaction corresponding to this newly proposed state update. If + // this is a taproot channel, then in order to validate the sighash, + // we'll need to call into the relevant tapscript methods. + if lc.channelState.ChanType.IsTaproot() { + localSession := lc.musigSessions.LocalSession - cSig, err := commitSigs.CommitSig.ToSignature() - if err != nil { - return err - } - if !cSig.Verify(sigHash, verifyKey) { - close(cancelChan) + // In order to verify the commitment sig, we'll need to craeate + // a proper musig partial sig. + var ( + partialS btcec.ModNScalar + partialSBytes [32]byte + ) + copy(partialSBytes[:], commitSigs.CommitSig.RawBytes()[32:]) + partialS.SetBytes(&partialSBytes) - // If we fail to validate their commitment signature, we'll - // generate a special error to send over the protocol. We'll - // include the exact signature and commitment we failed to - // verify against in order to aide debugging. - var txBytes bytes.Buffer - localCommitTx.Serialize(&txBytes) - return &InvalidCommitSigError{ - commitHeight: nextHeight, - commitSig: commitSigs.CommitSig.ToSignatureBytes(), - sigHash: sigHash, - commitTx: txBytes.Bytes(), + partialSig := musig2.PartialSignature{ + S: &partialS, + } + + _, err := localSession.VerifyCommitSig( + localCommitTx, &partialSig, + ) + if err != nil { + return err + } + } else { + sigHash, err := txscript.CalcWitnessSigHash( + multiSigScript, hashCache, txscript.SigHashAll, + localCommitTx, 0, int64(lc.channelState.Capacity), + ) + + verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey + + cSig, err := commitSigs.CommitSig.ToSignature() + if err != nil { + return err + } + if !cSig.Verify(sigHash, verifyKey) { + close(cancelChan) + + // If we fail to validate their commitment signature, + // we'll generate a special error to send over the + // protocol. We'll include the exact signature and + // commitment we failed to verify against in order to + // aide debugging. + var txBytes bytes.Buffer + localCommitTx.Serialize(&txBytes) + return &InvalidCommitSigError{ + commitHeight: nextHeight, + commitSig: commitSigs.CommitSig.ToSignatureBytes(), + sigHash: sigHash, + commitTx: txBytes.Bytes(), + } } } + if err != nil { + // TODO(roasbeef): fetchview has already mutated the HTLCs... + // * need to either roll-back, or make pure + return err + } // With the primary commitment transaction validated, we'll check each // of the HTLC validation jobs. @@ -4865,14 +4908,26 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { // The signature checks out, so we can now add the new commitment to // our local commitment chain. - localCommitmentView.sig = commitSig.ToSignatureBytes() localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() lc.localCommitChain.addCommitment(localCommitmentView) + if !lc.channelState.ChanType.IsTaproot() { + return nil + } + // At this point, we now have their next signing nonce, so we can // refresh our local session which allows us to verify more incoming // commitments. - return lc.musigSessions.LocalSession.Refresh(nextSigningNonce) + // + // TODO(roasbef): should isolate here re signer nonce, existing panic + newLocalSession, err := lc.musigSessions.LocalSession.Refresh( + *commitSigs.NextSignerNonce, + ) + if err != nil { + return err + } + lc.musigSessions.LocalSession = newLocalSession + return nil } // IsChannelClean returns true if neither side has pending commitments, neither @@ -5046,15 +5101,16 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c &lc.channelState.FundingOutpoint, ) - // TODO(roasbeef): refresh session here - - // We've now accepted+revoked a new commitment, so we'll send the - // remote party another verification nonce they can use to generate new - // commitments. - musigSession := lc.musigSessions - nextVerificationNonce := musigSession.LocalSession.VerificationNonce() - revocationMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: nextVerificationNonce, + // If this is a taproot channel, We've now accepted+revoked a new + // commitment, so we'll send the remote party another verification + // nonce they can use to generate new commitments. + if lc.channelState.ChanType.IsTaproot() { + musigSession := lc.musigSessions + nextVerificationNonce := musigSession.LocalSession.VerificationNonce() + // TODO(roasbeef): need new method here for verification nonce, in recv sig + revocationMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: nextVerificationNonce, + } } return revocationMsg, newCommitment.Htlcs, nil @@ -5287,11 +5343,14 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( // Now that we have a new verification nonce from them, we can refresh // our remote musig2 session which allows us to create another state. - err = lc.musigSessions.RemoteSession.Refresh( - revMsg.LocalNonce.Musig2Nonce, - ) - if err != nil { - return nil, nil, nil, nil, err + if lc.channelState.ChanType.IsTaproot() { + newRemoteSession, err := lc.musigSessions.RemoteSession.Refresh( + revMsg.LocalNonce.Musig2Nonce, + ) + if err != nil { + return nil, nil, nil, nil, err + } + lc.musigSessions.RemoteSession = newRemoteSession } // At this point, the revocation has been accepted, and we've rotated @@ -5919,7 +5978,7 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { localCommit := lc.channelState.LocalCommitment commitTx := localCommit.CommitTx.Copy() - // TODO(roasbeef): parametrize... + // TODO(roasbeef): update for taproot // * also want the full R sig as well theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) From 90194af46cd30c2e2b67fb0aac47450bb00d7447 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:53:23 -0800 Subject: [PATCH 74/87] lnwallet: add new public NewMusigChannelNonces method, use pubkey for nonce gen --- lnwallet/channel.go | 53 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index bfcdee191e8..9f75707c078 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -7852,22 +7852,40 @@ func (lc *LightningChannel) GenMusigNonces() (*MusigNoncePair, error) { lc.RLock() defer lc.RUnlock() - verificationNonce, err := musig2.GenNonces() + var err error + lc.nextNoncePair, err = NewMusigChannelNonces( + lc.channelState.LocalChanCfg.MultiSigKey.PubKey, + ) if err != nil { return nil, err } - signingNonce, err := musig2.GenNonces() + return lc.nextNoncePair, nil +} + +// NewMusigChannelNonces... +func NewMusigChannelNonces(pubKey *btcec.PublicKey) (*MusigNoncePair, error) { + pubKeyOpt := musig2.WithPublicKey(pubKey) + + verificationNonce, err := musig2.GenNonces(pubKeyOpt) if err != nil { return nil, err } - lc.nextNoncePair = &MusigNoncePair{ + signingNonce, err := musig2.GenNonces(pubKeyOpt) + if err != nil { + return nil, err + } + + return &MusigNoncePair{ LocalNonce: verificationNonce, RemoteNonce: signingNonce, - } + }, nil +} - return lc.nextNoncePair, nil +// HasRemoteNonces returns true if the channel has a remote nonce pair. +func (lc *LightningChannel) HasRemoteNonces() bool { + return lc.musigSessions != nil } // InitRemoteMusigNonces processes the remote musig nonces sent by the remote @@ -7915,3 +7933,28 @@ func (lc *LightningChannel) InitRemoteMusigNonces(nonces *MusigNoncePair) error return nil } + +// ChanType... +func (lc *LightningChannel) ChanType() channeldb.ChannelType { + lc.RLock() + defer lc.RUnlock() + + return lc.channelState.ChanType +} + +// FundingTxOut... +func (lc *LightningChannel) FundingTxOut() *wire.TxOut { + lc.RLock() + defer lc.RUnlock() + + return &lc.fundingOutput +} + +// MultiSigKeys... +func (lc *LightningChannel) MultiSigKeys() (keychain.KeyDescriptor, keychain.KeyDescriptor) { + lc.RLock() + defer lc.RUnlock() + + return lc.channelState.LocalChanCfg.MultiSigKey, + lc.channelState.RemoteChanCfg.MultiSigKey +} From f9da9a453c9f9b75ff43e336f4f243552bb4ee98 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:53:46 -0800 Subject: [PATCH 75/87] lnwallet: add new co-op close opts for providing musig2 session for taproot --- lnwallet/channel.go | 129 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 27 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 9f75707c078..698d9620dd6 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3287,7 +3287,6 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // Finally, we'll generate a sign descriptor to generate a // signature to give to the remote party for this commitment // transaction. Note we use the raw HTLC amount. - txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] sigJob.SignDesc = input.SignDescriptor{ KeyDesc: localChanCfg.HtlcBasePoint, SingleTweak: keyRing.LocalHtlcKeyTweak, @@ -6901,6 +6900,26 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, }, nil } +// chanCloseOpt... +type chanCloseOpt struct { + musigSession *MusigSession +} + +// ChanCloseOpt.. +type ChanCloseOpt func(*chanCloseOpt) + +// defaultCloseOpts... +func defaultCloseOpts() *chanCloseOpt { + return &chanCloseOpt{} +} + +// WithCoopCloseMusigSession... +func WithCoopCloseMusigSession(session *MusigSession) ChanCloseOpt { + return func(opts *chanCloseOpt) { + opts.musigSession = session + } +} + // CreateCloseProposal is used by both parties in a cooperative channel close // workflow to generate proposed close transactions and signatures. This method // should only be executed once all pending HTLCs (if any) on the channel have @@ -6912,8 +6931,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, // TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs, // settle any in flight. func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, - localDeliveryScript []byte, - remoteDeliveryScript []byte) (input.Signature, *chainhash.Hash, + localDeliveryScript []byte, remoteDeliveryScript []byte, + closeOpts ...ChanCloseOpt) (input.Signature, *chainhash.Hash, btcutil.Amount, error) { lc.Lock() @@ -6925,6 +6944,11 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, return nil, nil, 0, ErrChanClosing } + opts := defaultCloseOpts() + for _, optFunc := range closeOpts { + optFunc(opts) + } + // Get the final balances after subtracting the proposed fee, taking // care not to persist the adjusted balance, as the feeRate may change // during the channel closing process. @@ -6950,14 +6974,25 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, return nil, nil, 0, err } - // Finally, sign the completed cooperative closure transaction. As the - // initiator we'll simply send our signature over to the remote party, - // using the generated txid to be notified once the closure transaction - // has been confirmed. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(closeTx) - sig, err := lc.Signer.SignOutputRaw(closeTx, lc.signDesc) - if err != nil { - return nil, nil, 0, err + // If we have a co-op close musig session, then this is a taproot + // channel, so we'll generate a _partial_ signature. + var sig input.Signature + if opts.musigSession != nil { + sig, _, err = opts.musigSession.SignCommit(closeTx) + if err != nil { + return nil, nil, 0, err + } + } else { + // For regular channels we'll, sign the completed cooperative + // closure transaction. As the initiator we'll simply send our + // signature over to the remote party, using the generated txid + // to be notified once the closure transaction has been + // confirmed. + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(closeTx) + sig, err = lc.Signer.SignOutputRaw(closeTx, lc.signDesc) + if err != nil { + return nil, nil, 0, err + } } // As everything checks out, indicate in the channel status that a @@ -6978,7 +7013,8 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, func (lc *LightningChannel) CompleteCooperativeClose( localSig, remoteSig input.Signature, localDeliveryScript, remoteDeliveryScript []byte, - proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error) { + proposedFee btcutil.Amount, + closeOpts ...ChanCloseOpt) (*wire.MsgTx, btcutil.Amount, error) { lc.Lock() defer lc.Unlock() @@ -6989,6 +7025,11 @@ func (lc *LightningChannel) CompleteCooperativeClose( return nil, 0, ErrChanClosing } + opts := defaultCloseOpts() + for _, optFunc := range closeOpts { + optFunc(opts) + } + // Get the final balances after subtracting the proposed fee. ourBalance, theirBalance, err := CoopCloseBalance( lc.channelState.ChanType, lc.channelState.IsInitiator, @@ -7011,31 +7052,65 @@ func (lc *LightningChannel) CompleteCooperativeClose( // consensus rules such as being too big, or having any value with a // negative output. tx := btcutil.NewTx(closeTx) + prevOut := lc.signDesc.Output if err := blockchain.CheckTransactionSanity(tx); err != nil { return nil, 0, err } - hashCache := input.NewTxSigHashesV0Only(closeTx) - // Finally, construct the witness stack minding the order of the - // pubkeys+sigs on the stack. - ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - witness := input.SpendMultiSig( - lc.signDesc.WitnessScript, ourKey, localSig, theirKey, - remoteSig, + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + prevOut.PkScript, prevOut.Value, ) - closeTx.TxIn[0].Witness = witness + hashCache := txscript.NewTxSigHashes(closeTx, prevOutputFetcher) + + // Next, we'll complete the co-op close transaction. Depending on the + // set of options, we'll either do a regular p2wsh spend, or construct + // the final schnorr signature from a set of partial sigs. + if opts.musigSession != nil { + // For taproot channels, we'll use the attached session to + // combine the two partial signatures into a proper schnorr + // signature. + remoteSchnorrSig, _ := remoteSig.(*schnorr.Signature) + + var remotePartialSig MusigPartialSig + remotePartialSig.FromSchnorrShell(remoteSchnorrSig) + + // TODO(roasbeef): only need one sig combined? + //finalSchnorrSig, err := opts.musigSession.CombineSigs( + //localPartialSig.sig, remotePartialSig.sig, + //) + finalSchnorrSig, err := opts.musigSession.CombineSigs( + remotePartialSig.sig, + ) + if err != nil { + return nil, 0, fmt.Errorf("unable to combine "+ + "final co-op close sig: %w", err) + } + + // The witness for a keyspend is just the signature itself. + closeTx.TxIn[0].Witness = wire.TxWitness{ + finalSchnorrSig.Serialize(), + } + } else { + + // For regular channels, we'll need to , construct the witness + // stack minding the order of the pubkeys+sigs on the stack. + ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + witness := input.SpendMultiSig( + lc.signDesc.WitnessScript, ourKey, localSig, theirKey, + remoteSig, + ) + closeTx.TxIn[0].Witness = witness + + } // Validate the finalized transaction to ensure the output script is // properly met, and that the remote peer supplied a valid signature. - prevOut := lc.signDesc.Output vm, err := txscript.NewEngine( prevOut.PkScript, closeTx, 0, txscript.StandardVerifyFlags, nil, - hashCache, prevOut.Value, txscript.NewCannedPrevOutputFetcher( - prevOut.PkScript, prevOut.Value, - ), + hashCache, prevOut.Value, prevOutputFetcher, ) if err != nil { return nil, 0, err From 464e00909406aef7f5d34e9c5dd0b6710fa93b7a Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:54:03 -0800 Subject: [PATCH 76/87] lnwallet: add taproot case to testAddSettleWorkflow --- lnwallet/channel_test.go | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 04759603ffd..a7ac2e2b2fa 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -59,7 +59,9 @@ func assertOutputExistsByValue(t *testing.T, commitTx *wire.MsgTx, // testAddSettleWorkflow tests a simple channel scenario where Alice and Bob // add, the settle an HTLC between themselves. -func testAddSettleWorkflow(t *testing.T, tweakless bool) { +func testAddSettleWorkflow(t *testing.T, tweakless bool, + chanTypeModifier channeldb.ChannelType) { + // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. @@ -68,6 +70,10 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { chanType = channeldb.SingleFunderBit } + if chanTypeModifier != 0 { + chanType |= chanTypeModifier + } + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(chanType) require.NoError(t, err, "unable to create test channels") defer cleanUp() @@ -187,12 +193,18 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // Both commitment transactions should have three outputs, and one of // them should be exactly the amount of the HTLC. - if len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 { + numOutputs := 3 + if chanTypeModifier.HasAnchors() { + // In this case we expect two extra outputs as both sides need an + // anchor output. + numOutputs = 5 + } + if len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut) != numOutputs { t.Fatalf("alice should have three commitment outputs, instead "+ "have %v", len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut)) } - if len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 { + if len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut) != numOutputs { t.Fatalf("bob should have three commitment outputs, instead "+ "have %v", len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut)) @@ -324,9 +336,17 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { for _, tweakless := range []bool{true, false} { tweakless := tweakless t.Run(fmt.Sprintf("tweakless=%v", tweakless), func(t *testing.T) { - testAddSettleWorkflow(t, tweakless) + testAddSettleWorkflow(t, tweakless, 0) }) } + t.Run("anchors", func(t *testing.T) { + testAddSettleWorkflow( + t, true, channeldb.AnchorOutputsBit|channeldb.ZeroHtlcTxFeeBit, + ) + }) + t.Run("taproot", func(t *testing.T) { + testAddSettleWorkflow(t, true, channeldb.SimpleTaprootFeatureBit) + }) } // TestChannelZeroAddLocalHeight tests that we properly set the addCommitHeightLocal @@ -5356,7 +5376,14 @@ func TestInvalidCommitSigError(t *testing.T) { // Before the signature gets to Bob, we'll mutate it, such that the // signature is now actually invalid. - aliceNewCommit.CommitSig[0] ^= 88 + sigCopy := aliceNewCommit.CommitSig.Copy() + copyBytes := sigCopy.RawBytes() + copyBytes[0] ^= 80 + + aliceNewCommit.CommitSig, err = lnwire.NewSigFromSchnorrRawSignature( + copyBytes, + ) + require.NoError(t, err) // Bob should reject this new state, and return the proper error. err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) From ab91106da04288a0670cb4d3f7764e714fd50404 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:55:24 -0800 Subject: [PATCH 77/87] peer+lnpeer: add new wrapper struct to allow passing opaque opts to channel creation This'll be useful for when we need to bind the nonces we receive from a remote party to a channel state machine instance. --- htlcswitch/mock.go | 2 +- lnpeer/peer.go | 11 ++++++++++- peer/brontide.go | 19 ++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index b209e2c514a..f48d8249ea9 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -639,7 +639,7 @@ func (s *mockServer) Address() net.Addr { return nil } -func (s *mockServer) AddNewChannel(channel *channeldb.OpenChannel, +func (s *mockServer) AddNewChannel(channel *lnpeer.NewChannel, cancel <-chan struct{}) error { return nil diff --git a/lnpeer/peer.go b/lnpeer/peer.go index 465a41cb903..3749b9bea19 100644 --- a/lnpeer/peer.go +++ b/lnpeer/peer.go @@ -6,9 +6,18 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) +// NewChannel... +type NewChannel struct { + *channeldb.OpenChannel + + // ChanOpts... + ChanOpts []lnwallet.ChannelOpt +} + // Peer is an interface which represents a remote lightning node. type Peer interface { // SendMessage sends a variadic number of high-priority message to @@ -25,7 +34,7 @@ type Peer interface { // AddNewChannel adds a new channel to the peer. The channel should fail // to be added if the cancel channel is closed. - AddNewChannel(channel *channeldb.OpenChannel, cancel <-chan struct{}) error + AddNewChannel(newChan *NewChannel, cancel <-chan struct{}) error // WipeChannel removes the channel uniquely identified by its channel // point from all indexes associated with the peer. diff --git a/peer/brontide.go b/peer/brontide.go index b9741fae270..c869ea53ea1 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -86,7 +86,7 @@ type outgoingMsg struct { // the receiver of the request to report when the channel creation process has // completed. type newChannelMsg struct { - channel *channeldb.OpenChannel + channel *lnpeer.NewChannel err chan error } @@ -2294,9 +2294,10 @@ out: chanPoint := &newChan.FundingOutpoint chanID := lnwire.NewChanIDFromOutPoint(chanPoint) - // Only update RemoteNextRevocation if the channel is in the - // activeChannels map and if we added the link to the switch. - // Only active channels will be added to the switch. + // Only update RemoteNextRevocation if the channel is + // in the activeChannels map and if we added the link + // to the switch. Only active channels will be added + // to the switch. p.activeChanMtx.Lock() currentChan, ok := p.activeChannels[chanID] if ok && currentChan != nil { @@ -2326,6 +2327,8 @@ out: continue } + // TODO(roasbeef): don't also need to apply + // nonces here? get from chan reest continue } @@ -2333,7 +2336,8 @@ out: // set of active channels, so we can look it up later // easily according to its channel ID. lnChan, err := lnwallet.NewLightningChannel( - p.cfg.Signer, newChan, p.cfg.SigPool, + p.cfg.Signer, newChan.OpenChannel, + p.cfg.SigPool, newChan.ChanOpts..., ) if err != nil { p.activeChanMtx.Unlock() @@ -2720,6 +2724,7 @@ func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel, chanCloser := chancloser.NewChanCloser( chancloser.ChanCloseCfg{ Channel: channel, + Signer: p.cfg.Signer, BroadcastTx: p.cfg.Wallet.PublishTransaction, DisableChannel: func(op wire.OutPoint) error { return p.cfg.ChanStatusMgr.RequestDisable( @@ -3322,12 +3327,12 @@ func (p *Brontide) Address() net.Addr { // added if the cancel channel is closed. // // NOTE: Part of the lnpeer.Peer interface. -func (p *Brontide) AddNewChannel(channel *channeldb.OpenChannel, +func (p *Brontide) AddNewChannel(newChan *lnpeer.NewChannel, cancel <-chan struct{}) error { errChan := make(chan error, 1) newChanMsg := &newChannelMsg{ - channel: channel, + channel: newChan, err: errChan, } From 7643be5b4073931df69e9079e799f02c46718129 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:56:05 -0800 Subject: [PATCH 78/87] htlcswitch: pass along new musig2 nonce information to link --- htlcswitch/link.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index f2525ba86e3..486e92cb48b 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1891,7 +1891,11 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // We just received a new updates to our local commitment // chain, validate this new commitment, closing the link if // invalid. - err = l.channel.ReceiveNewCommitment(msg.CommitSig, msg.HtlcSigs) + err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{ + CommitSig: msg.CommitSig, + HtlcSigs: msg.HtlcSigs, + NextSignerNonce: &msg.RemoteNonce.Musig2Nonce, + }) if err != nil { // If we were unable to reconstruct their proposed // commitment, then we'll examine the type of error. If @@ -2205,7 +2209,7 @@ func (l *channelLink) updateCommitTx() error { return nil } - theirCommitSig, htlcSigs, pendingHTLCs, err := l.channel.SignNextCommitment() + newCommit, err := l.channel.SignNextCommitment() if err == lnwallet.ErrNoWindow { l.cfg.PendingCommitTicker.Resume() l.log.Trace("PendingCommitTicker resumed") @@ -2237,7 +2241,7 @@ func (l *channelLink) updateCommitTx() error { // pending). newUpdate := &contractcourt.ContractUpdate{ HtlcKey: contractcourt.RemotePendingHtlcSet, - Htlcs: pendingHTLCs, + Htlcs: newCommit.PendingHTLCs, } err = l.cfg.NotifyContractUpdate(newUpdate) if err != nil { @@ -2253,8 +2257,11 @@ func (l *channelLink) updateCommitTx() error { commitSig := &lnwire.CommitSig{ ChanID: l.ChanID(), - CommitSig: theirCommitSig, - HtlcSigs: htlcSigs, + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + RemoteNonce: &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: *newCommit.NextSignerNonce, + }, } l.cfg.Peer.SendMessage(false, commitSig) From a370180c2c3012a1f66040c7bf83f8392863ff78 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:57:57 -0800 Subject: [PATCH 79/87] htlcswitch: send new nonces in chan reest message, add recv logic --- htlcswitch/link.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 486e92cb48b..432414c2b0b 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" @@ -693,6 +694,22 @@ func (l *channelLink) syncChanStates() error { "ChannelPoint(%v)", l.channel.ChannelPoint()) } + // If this is a tarpoot channel, then in addition to the normal reest + // message, we'll also send our local+remote nonces as well. + if chanState.ChanType.IsTaproot() { + noncePair, err := l.channel.GenMusigNonces() + if err != nil { + return fmt.Errorf("unable to generate nonce "+ + "pair for chan: %w", err) + } + localChanSyncMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: noncePair.LocalNonce.PubNonce, + } + localChanSyncMsg.RemoteNonce = &lnwire.RemoteMusig2Nonce{ + Musig2Nonce: noncePair.RemoteNonce.PubNonce, + } + } + if err := l.cfg.Peer.SendMessage(true, localChanSyncMsg); err != nil { return fmt.Errorf("unable to send chan sync message for "+ "ChannelPoint(%v): %v", l.channel.ChannelPoint(), err) @@ -760,6 +777,27 @@ func (l *channelLink) syncChanStates() error { } } + // Before we process the ChanSync message, if this is a taproot + // channel, then we'll init our musig2 nonces state. + if chanState.ChanType.IsTaproot() { + l.log.Infof("initializing musig2 nonces") + + syncMsg := remoteChanSyncMsg + remoteNonces := &lnwallet.MusigNoncePair{ + LocalNonce: &musig2.Nonces{ + PubNonce: syncMsg.LocalNonce.Musig2Nonce, + }, + RemoteNonce: &musig2.Nonces{ + PubNonce: syncMsg.RemoteNonce.Musig2Nonce, + }, + } + err := l.channel.InitRemoteMusigNonces(remoteNonces) + if err != nil { + return fmt.Errorf("unable to init musig2 "+ + "nonces: %w", err) + } + } + // In any case, we'll then process their ChanSync message. l.log.Info("received re-establishment message from remote side") From f585b9c0685e839acdf8317bb0a98d8380d9c77c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:59:00 -0800 Subject: [PATCH 80/87] lnwallet/chancloser: add support for musig2 co-op closes w/ auto accept We implement a simpler version than the existing round based proposal, as with musig2 nonce semantics, it isn't possible to go back and "accept" an older proposal, as that was using an older nonce that has already been effectively revoked. --- lnwallet/chancloser/chancloser.go | 245 +++++++++++++++++++++++++----- 1 file changed, 211 insertions(+), 34 deletions(-) diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index 1af162f7dfb..a7c2618074a 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -4,14 +4,17 @@ import ( "bytes" "fmt" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -113,6 +116,16 @@ type Channel interface { // ShortChanID returns the scid of the channel. ShortChanID() lnwire.ShortChannelID + // ChanType.... + ChanType() channeldb.ChannelType + + // FundingTxOut returns the funding output of the channel. + FundingTxOut() *wire.TxOut + + // MultiSigKeys returns the local and remote multi-sig keys for the + // channel. + MultiSigKeys() (keychain.KeyDescriptor, keychain.KeyDescriptor) + // AbsoluteThawHeight returns the absolute thaw height of the channel. // If the channel is pending, or an unconfirmed zero conf channel, then // an error should be returned. @@ -127,14 +140,16 @@ type Channel interface { // of a valid signature, the chainhash of the final txid, and our final // balance in the created state. CreateCloseProposal(proposedFee btcutil.Amount, localDeliveryScript []byte, - remoteDeliveryScript []byte) (input.Signature, *chainhash.Hash, + remoteDeliveryScript []byte, + closeOpt ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash, btcutil.Amount, error) // CompleteCooperativeClose persistently "completes" the cooperative // close by producing a fully signed co-op close transaction. CompleteCooperativeClose(localSig, remoteSig input.Signature, localDeliveryScript, remoteDeliveryScript []byte, - proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error) + proposedFee btcutil.Amount, closeOpt ...lnwallet.ChanCloseOpt, + ) (*wire.MsgTx, btcutil.Amount, error) } // ChanCloseCfg holds all the items that a ChanCloser requires to carry out its @@ -143,6 +158,9 @@ type ChanCloseCfg struct { // Channel is the channel that should be closed. Channel Channel + // Signer... + Signer input.Signer + // BroadcastTx broadcasts the passed transaction to the network. BroadcastTx func(*wire.MsgTx, string) error @@ -229,6 +247,15 @@ type ChanCloser struct { // locallyInitiated is true if we initiated the channel close. locallyInitiated bool + + // musigSession is the MuSig session that we'll use to sign the closing + // transaction. + // + // NOTE: This is only populated if this is a taproot channel. + musigSession *lnwallet.MusigSession + + // musigNoncePair... + musigNoncePair *lnwallet.MusigNoncePair } // NewChanCloser creates a new instance of the channel closure given the passed @@ -277,19 +304,43 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { // closing script. shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript) - // Before closing, we'll attempt to send a disable update for the channel. - // We do so before closing the channel as otherwise the current edge policy - // won't be retrievable from the graph. + // If this is a taproot channel, then we'll need to also generate a + // nonce that'll be used sign the co-op close transaction offer. + if c.cfg.Channel.ChanType().IsTaproot() { + // TODO(roasbeef): temp, need a puibkey input, can expose main key + // otherwise -- gotta use the actual here here + localKey, _ := c.cfg.Channel.MultiSigKeys() + firstClosingNonce, err := musig2.GenNonces( + musig2.WithPublicKey(localKey.PubKey), + ) + if err != nil { + return nil, err + } + shutdown.Musig2Nonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: firstClosingNonce.PubNonce, + } + + chancloserLog.Infof("Initiating shutdown w/ nonce: %v", + spew.Sdump(firstClosingNonce.PubNonce)) + + c.musigNoncePair = &lnwallet.MusigNoncePair{ + LocalNonce: firstClosingNonce, + } + } + + // Before closing, we'll attempt to send a disable update for the + // channel. We do so before closing the channel as otherwise the + // current edge policy won't be retrievable from the graph. if err := c.cfg.DisableChannel(c.chanPoint); err != nil { chancloserLog.Warnf("Unable to disable channel %v on close: %v", c.chanPoint, err) } - // Before continuing, mark the channel as cooperatively closed with a nil - // txn. Even though we haven't negotiated the final txn, this guarantees - // that our listchannels rpc will be externally consistent, and reflect - // that the channel is being shutdown by the time the closing request - // returns. + // Before continuing, mark the channel as cooperatively closed with a + // nil txn. Even though we haven't negotiated the final txn, this + // guarantees that our listchannels rpc will be externally consistent, + // and reflect that the channel is being shutdown by the time the + // closing request returns. err := c.cfg.Channel.MarkCoopBroadcasted(nil, c.locallyInitiated) if err != nil { return nil, err @@ -358,6 +409,7 @@ func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose { // NOTE: This method will PANIC if the underlying channel implementation isn't // the desired type. func (c *ChanCloser) Channel() *lnwallet.LightningChannel { + // TODO(roasbeef): remove this return c.cfg.Channel.(*lnwallet.LightningChannel) } @@ -432,8 +484,9 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // as otherwise, this is an attempted invalid state transition. shutdownMsg, ok := msg.(*lnwire.Shutdown) if !ok { - return nil, false, fmt.Errorf("expected lnwire.Shutdown, instead "+ - "have %v", spew.Sdump(msg)) + return nil, false, fmt.Errorf("expected "+ + "lnwire.Shutdown, instead have %v", + spew.Sdump(msg)) } // As we're the responder to this shutdown (the other party @@ -477,6 +530,15 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, return nil, false, err } + // If this is a taproot channel, then we'll want to stash the + // remote nonces so we can properly create a new musig + // session for signing. + if c.cfg.Channel.ChanType().IsTaproot() { + c.musigNoncePair.RemoteNonce = &musig2.Nonces{ + PubNonce: shutdownMsg.Musig2Nonce.Musig2Nonce, + } + } + chancloserLog.Infof("ChannelPoint(%v): responding to shutdown", c.chanPoint) @@ -494,7 +556,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, if chanInitiator { closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) if err != nil { - return nil, false, err + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) } msgsToSend = append(msgsToSend, closeSigned) } @@ -534,6 +597,15 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // closing transaction should look like. c.state = closeFeeNegotiation + // If this is a taproot channel, then we'll want to stash the + // local+remote nonces so we can properly create a new musig + // session for signing. + if c.cfg.Channel.ChanType().IsTaproot() { + c.musigNoncePair.RemoteNonce = &musig2.Nonces{ + PubNonce: shutdownMsg.Musig2Nonce.Musig2Nonce, + } + } + chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+ "entering fee negotiation", c.chanPoint) @@ -543,7 +615,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, if c.cfg.Channel.IsInitiator() { closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) if err != nil { - return nil, false, err + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) } return []lnwire.Message{closeSigned}, false, nil @@ -563,14 +636,46 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, "instead have %v", spew.Sdump(msg)) } + // If this is a taproot channel, then the sig will come along + // with a nonce to use for the _next_ proposed state, so we'll + // stash that now. + /*if c.cfg.Channel.ChanType().IsTaproot() { + c.musigNoncePair.RemoteNonce = &musig2.Nonces{ + PubNonce: closeSignedMsg.Musig2Nonce.Musig2Nonce, + } + }*/ + // We'll compare the proposed total fee, to what we've proposed during // the negotiations. If it doesn't match any of our prior offers, then // we'll attempt to ratchet the fee closer to remoteProposedFee := closeSignedMsg.FeeSatoshis - if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok { - // We'll now attempt to ratchet towards a fee deemed acceptable by - // both parties, factoring in our ideal fee rate, and the last - // proposed fee by both sides. + + // For taproot channels, since nonces are involved, we can't do + // the existing co-op close negotiation process without going + // to a fully round based model. Rather than do this, we'll + // just accept the very first offer by the initiator. + if c.cfg.Channel.ChanType().IsTaproot() && + !c.cfg.Channel.IsInitiator() { + chancloserLog.Infof("ChannelPoint(%v) accepting "+ + "initiator fee of %v", c.chanPoint, + remoteProposedFee) + + // To auto-accept the initiators proposal, we'll just + // send back a signature w/ the same offer. + closeSigned, err := c.proposeCloseSigned( + remoteProposedFee, + ) + if err != nil { + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) + } + + return []lnwire.Message{closeSigned}, false, nil + + } else if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok { + // We'll now attempt to ratchet towards a fee deemed + // acceptable by both parties, factoring in our ideal + // fee rate, and the last proposed fee by both sides. feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat, c.lastFeeProposal, remoteProposedFee, ) @@ -580,17 +685,21 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, c.maxFee) } - // With our new fee proposal calculated, we'll craft a new close - // signed signature to send to the other party so we can continue - // the fee negotiation process. + // With our new fee proposal calculated, we'll craft a + // new close signed signature to send to the other + // party so we can continue the fee negotiation + // process. closeSigned, err := c.proposeCloseSigned(feeProposal) if err != nil { - return nil, false, err + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) } - // If the compromise fee doesn't match what the peer proposed, then - // we'll return this latest close signed message so we can continue - // negotiation. + // TODO(roasbeef): need to clean up old musig2 session + + // If the compromise fee doesn't match what the peer + // proposed, then we'll return this latest close signed + // message so we can continue negotiation. if feeProposal != remoteProposedFee { chancloserLog.Debugf("ChannelPoint(%v): close tx fee "+ "disagreement, continuing negotiation", c.chanPoint) @@ -605,19 +714,39 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // craft the final closing transaction so we can broadcast it to the // network. matchingSig := c.priorFeeOffers[remoteProposedFee].Signature + + // At this point, we can complete the final co-op close + // signature. For taproot channels, we'll need to interpret the + // 64 byte signatures as an actual schnorr sig. + if c.cfg.Channel.ChanType().IsTaproot() { + // TODO(roasbeef): tempt ting, need either sig w/ nonce + // or diff type + matchingSig.ForceSchnorr() + closeSignedMsg.Signature.ForceSchnorr() + } + localSig, err := matchingSig.ToSignature() if err != nil { return nil, false, err } - remoteSig, err := closeSignedMsg.Signature.ToSignature() if err != nil { return nil, false, err } + // For taproot channels, we'll need to pass along the session + // so the final combined signature can be created. + var closeOpts []lnwallet.ChanCloseOpt + if c.cfg.Channel.ChanType().IsTaproot() { + closeOpts = append( + closeOpts, + lnwallet.WithCoopCloseMusigSession(c.musigSession), + ) + } + closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose( - localSig, remoteSig, c.localDeliveryScript, c.remoteDeliveryScript, - remoteProposedFee, + localSig, remoteSig, c.localDeliveryScript, + c.remoteDeliveryScript, remoteProposedFee, closeOpts..., ) if err != nil { return nil, false, err @@ -680,19 +809,51 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // transaction for a channel based on the prior fee negotiations and our current // compromise fee. func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) { + var ( + closeOpts []lnwallet.ChanCloseOpt + err error + ) + + // If this is a taproot channel, then we'll include the musig session + // generated for the next co-op close negotiation round. + if c.cfg.Channel.ChanType().IsTaproot() { + localKey, remoteKey := c.cfg.Channel.MultiSigKeys() + c.musigSession, err = lnwallet.NewMusigSession( + *c.musigNoncePair, localKey, remoteKey, c.cfg.Signer, + c.cfg.Channel.FundingTxOut(), false, + ) + + closeOpts = append( + closeOpts, + lnwallet.WithCoopCloseMusigSession(c.musigSession), + ) + } + rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal( - fee, c.localDeliveryScript, c.remoteDeliveryScript, + fee, c.localDeliveryScript, c.remoteDeliveryScript, closeOpts..., ) if err != nil { return nil, err } - // We'll note our last signature and proposed fee so when the remote party - // responds we'll be able to decide if we've agreed on fees or not. c.lastFeeProposal = fee - parsedSig, err := lnwire.NewSigFromSignature(rawSig) - if err != nil { - return nil, err + + // We'll note our last signature and proposed fee so when the remote + // party responds we'll be able to decide if we've agreed on fees or + // not. + var parsedSig lnwire.Sig + if c.cfg.Channel.ChanType().IsTaproot() { + parsedSig, err = lnwire.NewSigFromSchnorrRawSignature( + rawSig.Serialize(), + ) + if err != nil { + return nil, err + } + } else { + parsedSig, err = lnwire.NewSigFromSignature(rawSig) + if err != nil { + return nil, err + } } chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+ @@ -707,6 +868,22 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign // accepts our offer. This way, we don't have to re-sign. c.priorFeeOffers[fee] = closeSignedMsg + // Finally, before give the final co-op signed message, we'll generate + // a new local musig nonce. + if c.cfg.Channel.ChanType().IsTaproot() { + localKey, _ := c.cfg.Channel.MultiSigKeys() + c.musigNoncePair.LocalNonce, err = musig2.GenNonces( + musig2.WithPublicKey(localKey.PubKey), + ) + if err != nil { + return nil, err + } + + closeSignedMsg.Musig2Nonce = &lnwire.LocalMusig2Nonce{ + Musig2Nonce: c.musigNoncePair.LocalNonce.PubNonce, + } + } + return closeSignedMsg, nil } From 5e68ba87c2ffce58f2cadf45a86538522538265c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 17:59:37 -0800 Subject: [PATCH 81/87] rpc: add support for parsing musig2 openchannel requests --- lnrpc/lightning.pb.go | 803 ++++++++++++++++++----------------- lnrpc/lightning.proto | 7 + lnrpc/lightning.swagger.json | 5 +- rpcserver.go | 51 ++- 4 files changed, 456 insertions(+), 410 deletions(-) diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index ff1073173e3..8af55bdc2b7 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -179,6 +179,8 @@ const ( //to guarantee that the channel initiator has no incentives to close a leased //channel before its maturity date. CommitmentType_SCRIPT_ENFORCED_LEASE CommitmentType = 4 + // TODO(roasbeef): need script enforce mirror type for the above as well? + CommitmentType_SIMPLE_TAPROOT CommitmentType = 5 ) // Enum value maps for CommitmentType. @@ -189,6 +191,7 @@ var ( 2: "STATIC_REMOTE_KEY", 3: "ANCHORS", 4: "SCRIPT_ENFORCED_LEASE", + 5: "SIMPLE_TAPROOT", } CommitmentType_value = map[string]int32{ "UNKNOWN_COMMITMENT_TYPE": 0, @@ -196,6 +199,7 @@ var ( "STATIC_REMOTE_KEY": 2, "ANCHORS": 3, "SCRIPT_ENFORCED_LEASE": 4, + "SIMPLE_TAPROOT": 5, } ) @@ -19483,412 +19487,413 @@ var file_lightning_proto_rawDesc = []byte{ 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, - 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x78, - 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, - 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, - 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, - 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, - 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, - 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, - 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, - 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, - 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, - 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, - 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, - 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, - 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, - 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, - 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, - 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, - 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, - 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, - 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, - 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, - 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, - 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, - 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, - 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, - 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xd9, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, - 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, - 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, - 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, - 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, - 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, - 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, - 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, - 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, - 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, - 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, - 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, - 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, - 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, - 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, - 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, - 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, - 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, - 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, - 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, - 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, - 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, - 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, - 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, - 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, + 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x8c, + 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, + 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, + 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, + 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, + 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, + 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, + 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, + 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x2a, 0x61, 0x0a, + 0x09, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, + 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, + 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, + 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, + 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, + 0x2a, 0x60, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, + 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, + 0x43, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, + 0x48, 0x54, 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, + 0x10, 0x04, 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, + 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, + 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, + 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, + 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, + 0x45, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, + 0x2a, 0x3b, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xd9, 0x01, + 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, + 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, + 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, + 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, + 0x12, 0x27, 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, + 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, 0x0a, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, + 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, + 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, + 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, + 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, + 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, + 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, + 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, + 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, + 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, + 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, + 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, + 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, + 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, + 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, + 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, + 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, + 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, - 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, - 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, - 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, - 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, - 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, - 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, - 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, - 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, - 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, - 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, - 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, - 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, - 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, - 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, 0x0b, 0x0a, 0x07, - 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, - 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, - 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, - 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, - 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, - 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, - 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, - 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0x8f, 0x26, 0x0a, 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, - 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, - 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, + 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, + 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, + 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, + 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, + 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, + 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, + 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, + 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, + 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x17, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, 0x12, 0x0b, + 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, 0x0a, 0x0d, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, + 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, + 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, + 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, + 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, + 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, + 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, + 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0x8f, 0x26, 0x0a, 0x09, 0x4c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, + 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, - 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, + 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, + 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x65, + 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, + 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x69, + 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x17, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, - 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, - 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, - 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, - 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, - 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, - 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, - 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, - 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, - 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, - 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, - 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, - 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, - 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x65, + 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, + 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, + 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, + 0x12, 0x53, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, + 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, + 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, - 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, - 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, - 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, - 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, + 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, + 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, + 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, + 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, + 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, + 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, + 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, + 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, + 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, - 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, - 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, + 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, - 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, - 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, - 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, - 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, - 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, - 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, - 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, - 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, - 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, - 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, - 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, - 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, - 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, - 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, - 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, - 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, - 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, + 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, + 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, + 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, + 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, + 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, + 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, + 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, + 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, + 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x28, 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index b1716e34004..c7451f31dd8 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -1320,6 +1320,13 @@ enum CommitmentType { channel before its maturity date. */ SCRIPT_ENFORCED_LEASE = 4; + + /* + A channel that uses musig2 for the funding output, and the new tapscript + features where relevant. + */ + // TODO(roasbeef): need script enforce mirror type for the above as well? + SIMPLE_TAPROOT = 5; } message ChannelConstraints { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 5007e143cfa..70ec21e41d0 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -3982,10 +3982,11 @@ "LEGACY", "STATIC_REMOTE_KEY", "ANCHORS", - "SCRIPT_ENFORCED_LEASE" + "SCRIPT_ENFORCED_LEASE", + "SIMPLE_TAPROOT" ], "default": "UNKNOWN_COMMITMENT_TYPE", - "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date." + "title": "- UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date.\n - SIMPLE_TAPROOT: TODO(roasbeef): need script enforce mirror type for the above as well?" }, "lnrpcConnectPeerRequest": { "type": "object", diff --git a/rpcserver.go b/rpcserver.go index 95d0b136d54..3180f5b0e16 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1816,10 +1816,12 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool, // With all the parts assembled, we can now make the canned assembler // to pass into the wallet. + // + // TODO(roasbeef): update to support musig2 return chanfunding.NewCannedAssembler( chanPointShim.ThawHeight, *chanPoint, btcutil.Amount(chanPointShim.Amt), &localKeyDesc, - remoteKey, initiator, + remoteKey, initiator, false, ), nil } @@ -2066,11 +2068,39 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, *channelType = lnwire.ChannelType(*fv) + case lnrpc.CommitmentType_SIMPLE_TAPROOT: + // If the taproot channel type is being set, then the channel + // MUST be private (unadvertised) for now. + if !in.Private { + return nil, fmt.Errorf("taproot channels must be " + + "private") + } + + channelType = new(lnwire.ChannelType) + fv := lnwire.NewRawFeatureVector( + lnwire.SimpleTaprootChannelsRequired, + ) + + // TODO(roasbeef): no need for the rest as they're now + // implicit? + + if in.ZeroConf { + fv.Set(lnwire.ZeroConfRequired) + } + + if in.ScidAlias { + fv.Set(lnwire.ScidAliasRequired) + } + + *channelType = lnwire.ChannelType(*fv) + default: return nil, fmt.Errorf("unhandled request channel type %v", in.CommitmentType) } + // TODO(roasbeef): make taproot the default chan type? + // Instruct the server to trigger the necessary events to attempt to // open a new channel. A stream is returned in place, this stream will // be used to consume updates of the state of the pending channel. @@ -3957,19 +3987,22 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { // Extract the commitment type from the channel type flags. We must // first check whether it has anchors, since in that case it would also // be tweakless. - if chanType.HasLeaseExpiration() { + switch { + case chanType.IsTaproot(): + return lnrpc.CommitmentType_SIMPLE_TAPROOT + + case chanType.HasLeaseExpiration(): return lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE - } - if chanType.HasAnchors() { + case chanType.HasAnchors(): return lnrpc.CommitmentType_ANCHORS - } - if chanType.IsTweakless() { + case chanType.IsTweakless(): return lnrpc.CommitmentType_STATIC_REMOTE_KEY - } + default: - return lnrpc.CommitmentType_LEGACY + return lnrpc.CommitmentType_LEGACY + } } // createChannelConstraint creates a *lnrpc.ChannelConstraints using the @@ -5933,7 +5966,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context, // within the HTLC. // // TODO(roasbeef): should return a slice of routes in reality -// * create separate PR to send based on well formatted route +// - create separate PR to send based on well formatted route func (r *rpcServer) QueryRoutes(ctx context.Context, in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) { From 0a18d3ebee33aae39adb7427b94468a9dfbdf035 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 30 Dec 2022 18:00:09 -0800 Subject: [PATCH 82/87] cmd/lncli: add taproot chan parsing support for openchannel --- cmd/lncli/cmd_open_channel.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/cmd/lncli/cmd_open_channel.go b/cmd/lncli/cmd_open_channel.go index a6cd8ef68cb..325156beb2a 100644 --- a/cmd/lncli/cmd_open_channel.go +++ b/cmd/lncli/cmd_open_channel.go @@ -59,8 +59,9 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to text file): ` // of memory issues or other weird errors. psbtMaxFileSize = 1024 * 1024 - channelTypeTweakless = "tweakless" - channelTypeAnchors = "anchors" + channelTypeTweakless = "tweakless" + channelTypeAnchors = "anchors" + channelTypeSimpleTaproot = "taproot" ) // TODO(roasbeef): change default number of confirmations. @@ -207,8 +208,9 @@ var openChannelCommand = cli.Command{ cli.StringFlag{ Name: "channel_type", Usage: fmt.Sprintf("(optional) the type of channel to "+ - "propose to the remote peer (%q, %q)", - channelTypeTweakless, channelTypeAnchors), + "propose to the remote peer (%q, %q, %q)", + channelTypeTweakless, channelTypeAnchors, + channelTypeSimpleTaproot), }, cli.BoolFlag{ Name: "zero_conf", @@ -339,6 +341,8 @@ func openChannel(ctx *cli.Context) error { req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY case channelTypeAnchors: req.CommitmentType = lnrpc.CommitmentType_ANCHORS + case channelTypeSimpleTaproot: + req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT default: return fmt.Errorf("unsupported channel type %v", channelType) } @@ -388,15 +392,16 @@ func openChannel(ctx *cli.Context) error { // protocol involves several steps between the RPC server and the CLI client: // // RPC server CLI client -// | | -// | |<------open channel (stream)-----| -// | |-------ready for funding----->| | -// | |<------PSBT verify------------| | -// | |-------ready for signing----->| | -// | |<------PSBT finalize----------| | -// | |-------channel pending------->| | -// | |-------channel open------------->| -// | | +// +// | | +// | |<------open channel (stream)-----| +// | |-------ready for funding----->| | +// | |<------PSBT verify------------| | +// | |-------ready for signing----->| | +// | |<------PSBT finalize----------| | +// | |-------channel pending------->| | +// | |-------channel open------------->| +// | | func openChannelPsbt(rpcCtx context.Context, ctx *cli.Context, client lnrpc.LightningClient, req *lnrpc.OpenChannelRequest) error { From 5ba9ed532998c5e3447bb1d956731b45b1a0253f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 11 Jan 2023 19:20:36 -0800 Subject: [PATCH 83/87] multi: switch to using new partial_sig TLV type for musig sigs --- funding/manager.go | 200 +++++++++--------- htlcswitch/link.go | 38 ++-- lnwallet/chancloser/chancloser.go | 164 +++++++-------- lnwallet/channel.go | 202 +++++++------------ lnwallet/musig2_session.go | 324 +++++++++++++++--------------- lnwallet/reservation.go | 5 - lnwallet/sigpool.go | 7 +- lnwallet/wallet.go | 86 +++----- lnwire/accept_channel.go | 24 +-- lnwire/channel_reestablish.go | 24 +-- lnwire/closing_signed.go | 25 ++- lnwire/commit_sig.go | 23 +-- lnwire/funding_created.go | 47 ++++- lnwire/funding_locked.go | 26 +-- lnwire/funding_signed.go | 51 ++++- lnwire/lnwire_test.go | 59 ++++-- lnwire/musig2.go | 42 +--- lnwire/open_channel.go | 26 +-- lnwire/partial_sig.go | 102 ++++++++++ lnwire/revoke_and_ack.go | 8 +- lnwire/shutdown.go | 8 +- 21 files changed, 766 insertions(+), 725 deletions(-) create mode 100644 lnwire/partial_sig.go diff --git a/funding/manager.go b/funding/manager.go index d04551dadbd..b95a339abd4 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -512,7 +512,7 @@ type Manager struct { // NOTE: This map is protected by the nonceMtx above. // // TODO(roasbeef): replace w/ generic concurrent map - pendingMusigNonces map[lnwire.ChannelID]*lnwallet.MusigNoncePair + pendingMusigNonces map[lnwire.ChannelID]*musig2.Nonces // activeReservations is a map which houses the state of all pending // funding workflows. @@ -603,7 +603,7 @@ func NewFundingManager(cfg Config) (*Manager, error) { fundingRequests: make(chan *InitFundingMsg, msgBufferSize), localDiscoverySignals: make(map[lnwire.ChannelID]chan struct{}), handleFundingLockedBarriers: make(map[lnwire.ChannelID]struct{}), - pendingMusigNonces: make(map[lnwire.ChannelID]*lnwallet.MusigNoncePair), + pendingMusigNonces: make(map[lnwire.ChannelID]*musig2.Nonces), quit: make(chan struct{}), }, nil } @@ -1669,10 +1669,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, }, UpfrontShutdown: msg.UpfrontShutdownScript, LocalNonce: &musig2.Nonces{ - PubNonce: msg.LocalNonce.Musig2Nonce, - }, - RemoteNonce: &musig2.Nonces{ - PubNonce: msg.RemoteNonce.Musig2Nonce, + PubNonce: *msg.LocalNonce, }, } err = reservation.ProcessSingleContribution(remoteContribution) @@ -1689,17 +1686,11 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, ourContribution := reservation.OurContribution() - var ( - localNonce *lnwire.LocalMusig2Nonce - remoteNonce *lnwire.RemoteMusig2Nonce - ) + var localNonce *lnwire.Musig2Nonce if commitType.IsTaproot() { - localNonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: ourContribution.LocalNonce.PubNonce, - } - remoteNonce = &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: ourContribution.RemoteNonce.PubNonce, - } + localNonce = (*lnwire.Musig2Nonce)( + &ourContribution.LocalNonce.PubNonce, + ) } // With the initiator's contribution recorded, respond with our @@ -1723,7 +1714,6 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, ChannelType: chanTypeFeatureBits, LeaseExpiry: msg.LeaseExpiry, LocalNonce: localNonce, - RemoteNonce: remoteNonce, } if err := peer.SendMessage(true, &fundingAccept); err != nil { @@ -1912,10 +1902,7 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer, }, UpfrontShutdown: msg.UpfrontShutdownScript, LocalNonce: &musig2.Nonces{ - PubNonce: msg.LocalNonce.Musig2Nonce, - }, - RemoteNonce: &musig2.Nonces{ - PubNonce: msg.RemoteNonce.Musig2Nonce, + PubNonce: *msg.LocalNonce, }, } err = resCtx.reservation.ProcessContribution(remoteContribution) @@ -2092,19 +2079,32 @@ func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx, log.Infof("Generated ChannelPoint(%v) for pending_id(%x)", outPoint, pendingChanID[:]) - // TODO(roasbeef): convert signature for taproot stuff - var err error fundingCreated := &lnwire.FundingCreated{ PendingChannelID: pendingChanID, FundingPoint: *outPoint, } - fundingCreated.CommitSig, err = lnwire.NewSigFromSignature(sig) - if err != nil { - log.Errorf("Unable to parse signature: %v", err) - f.failFundingFlow(resCtx.peer, pendingChanID, err) - return + + // If this is a taproot channel, then we'll need to populate the musig2 + // partial sig field instead of the regaulr commit sig field. + if resCtx.reservation.IsTaproot() { + partialSig, ok := sig.(*lnwallet.MusigPartialSig) + if !ok { + log.Errorf("expected musig partial sig, got %T", sig) + f.failFundingFlow(resCtx.peer, pendingChanID, err) + return + } + + fundingCreated.PartialSig = partialSig.ToWireSig() + } else { + fundingCreated.CommitSig, err = lnwire.NewSigFromSignature(sig) + if err != nil { + log.Errorf("Unable to parse signature: %v", err) + f.failFundingFlow(resCtx.peer, pendingChanID, err) + return + } } + if err := resCtx.peer.SendMessage(true, fundingCreated); err != nil { log.Errorf("Unable to send funding complete message: %v", err) f.failFundingFlow(resCtx.peer, pendingChanID, err) @@ -2138,19 +2138,21 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer, log.Infof("completing pending_id(%x) with ChannelPoint(%v)", pendingChanID[:], fundingOut) - // If this is a taproot channel, then we'll need to force the - // schnorr encoding. - // - // TODO(roasbeef): remove after nonce ting + // For taproot channels, the commit signature is actually the partial + // signature. Otherwise, we can convert the ECDSA commit signature into + // our internal input.Signature type. + var commitSig input.Signature if resCtx.reservation.IsTaproot() { - msg.CommitSig.ForceSchnorr() - } - - commitSig, err := msg.CommitSig.ToSignature() - if err != nil { - log.Errorf("unable to parse signature: %v", err) - f.failFundingFlow(peer, pendingChanID, err) - return + commitSig = new(lnwallet.MusigPartialSig).FromWireSig( + msg.PartialSig, + ) + } else { + commitSig, err = msg.CommitSig.ToSignature() + if err != nil { + log.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(peer, pendingChanID, err) + return + } } // With all the necessary data available, attempt to advance the @@ -2212,21 +2214,34 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer, log.Infof("sending FundingSigned for pending_id(%x) over "+ "ChannelPoint(%v)", pendingChanID[:], fundingOut) - // With their signature for our version of the commitment transaction - // verified, we can now send over our signature to the remote peer. - _, sig := resCtx.reservation.OurSignatures() - ourCommitSig, err := lnwire.NewSigFromSignature(sig) - if err != nil { - log.Errorf("unable to parse signature: %v", err) - f.failFundingFlow(peer, pendingChanID, err) - deleteFromDatabase() - return + fundingSigned := &lnwire.FundingSigned{ + ChanID: channelID, } - fundingSigned := &lnwire.FundingSigned{ - ChanID: channelID, - CommitSig: ourCommitSig, + // For taproot channels, we'll need to send over a partial signature + // that includes the nonce along sidethe signature. + _, sig := resCtx.reservation.OurSignatures() + if resCtx.reservation.IsTaproot() { + partialSig, ok := sig.(*lnwallet.MusigPartialSig) + if !ok { + log.Errorf("expected musig partial sig, got %T", sig) + f.failFundingFlow(resCtx.peer, pendingChanID, err) + return + } + + fundingSigned.PartialSig = partialSig.ToWireSig() + } else { + fundingSigned.CommitSig, err = lnwire.NewSigFromSignature(sig) + if err != nil { + log.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(peer, pendingChanID, err) + deleteFromDatabase() + return + } } + + // With their signature for our version of the commitment transaction + // verified, we can now send over our signature to the remote peer. if err := peer.SendMessage(true, fundingSigned); err != nil { log.Errorf("unable to send FundingSigned message: %v", err) f.failFundingFlow(peer, pendingChanID, err) @@ -2323,14 +2338,21 @@ func (f *Manager) handleFundingSigned(peer lnpeer.Peer, msg.CommitSig.ForceSchnorr() } - // The remote peer has responded with a signature for our commitment - // transaction. We'll verify the signature for validity, then commit - // the state to disk as we can now open the channel. - commitSig, err := msg.CommitSig.ToSignature() - if err != nil { - log.Errorf("Unable to parse signature: %v", err) - f.failFundingFlow(peer, pendingChanID, err) - return + // For taproot channels, the commit signature is actually the partial + // signature. Otherwise, we can convert the ECDSA commit signature into + // our internal input.Signature type. + var commitSig input.Signature + if resCtx.reservation.IsTaproot() { + commitSig = new(lnwallet.MusigPartialSig).FromWireSig( + msg.PartialSig, + ) + } else { + commitSig, err = msg.CommitSig.ToSignature() + if err != nil { + log.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(peer, pendingChanID, err) + return + } } completeChan, err := resCtx.reservation.CompleteReservation( @@ -2785,6 +2807,7 @@ func (f *Manager) handleFundingConfirmation( // Now that that the channel has been fully confirmed, we'll request // that the wallet fully verify this channel to ensure that it can be // used. + log.Infof("taproot: %v", completeChan.ChanType.IsTaproot()) err := f.cfg.Wallet.ValidateChannel(completeChan, confChannel.fundingTx) if err != nil { // TODO(roasbeef): delete chan state? @@ -2877,14 +2900,14 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel, chanID) f.nonceMtx.Lock() - localNoncePair, ok := f.pendingMusigNonces[chanID] + localNonce, ok := f.pendingMusigNonces[chanID] if !ok { // If we don't have any nonces generated yet for this // first state, then we'll generate them now and stow // them away. When we receive the funding locked // message, we'll then pass along this same set of // nonces. - newNonces, err := channel.GenMusigNonces() + newNonce, err := channel.GenMusigNonces() if err != nil { f.nonceMtx.Unlock() return err @@ -2892,17 +2915,14 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel, // Now that we've generated the nonce for this channel, // we'll store it in the set of pending nonces. - localNoncePair = newNonces - f.pendingMusigNonces[chanID] = localNoncePair + localNonce = newNonce + f.pendingMusigNonces[chanID] = localNonce } f.nonceMtx.Unlock() - fundingLockedMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: localNoncePair.LocalNonce.PubNonce, - } - fundingLockedMsg.RemoteNonce = &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: localNoncePair.RemoteNonce.PubNonce, - } + fundingLockedMsg.LocalNonce = (*lnwire.Musig2Nonce)( + &localNonce.PubNonce, + ) } // If the channel negotiated the option-scid-alias feature bit, we'll @@ -3571,11 +3591,11 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, var chanOpts []lnwallet.ChannelOpt if channel.ChanType.IsTaproot() { f.nonceMtx.Lock() - localNonces, ok := f.pendingMusigNonces[chanID] + localNonce, ok := f.pendingMusigNonces[chanID] if !ok { // If there's no pending nonce for this channel ID, // then we'll generate one now. - noncePair, err := lnwallet.NewMusigChannelNonces( + verNonce, err := lnwallet.NewMusigVerificationNonce( channel.LocalChanCfg.MultiSigKey.PubKey, ) if err != nil { @@ -3585,30 +3605,21 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, return } - localNonces = noncePair - f.pendingMusigNonces[chanID] = localNonces + localNonce = verNonce + f.pendingMusigNonces[chanID] = localNonce } f.nonceMtx.Unlock() - remoteNonces := &lnwallet.MusigNoncePair{ - LocalNonce: &musig2.Nonces{ - PubNonce: msg.LocalNonce.Musig2Nonce, - }, - RemoteNonce: &musig2.Nonces{ - PubNonce: msg.RemoteNonce.Musig2Nonce, - }, - } - log.Infof("ChanID(%v): applying local+remote musig2 nonces", chanID) chanOpts = append( chanOpts, - lnwallet.WithLocalMusigNonces(localNonces), - lnwallet.WithRemoteMusigNonces(remoteNonces), + lnwallet.WithLocalMusigNonces(localNonce), + lnwallet.WithRemoteMusigNonces(&musig2.Nonces{ + PubNonce: *msg.LocalNonce, + }), ) - - // TODO(roasbeef): case of taproot chan w/ nonces not sent } // Launch a defer so we _ensure_ that the channel barrier is properly @@ -4217,17 +4228,11 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { log.Infof("Starting funding workflow with %v for pending_id(%x), "+ "committype=%v", msg.Peer.Address(), chanID, commitType) - var ( - localNonce *lnwire.LocalMusig2Nonce - remoteNonce *lnwire.RemoteMusig2Nonce - ) + var localNonce *lnwire.Musig2Nonce if commitType.IsTaproot() { - localNonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: ourContribution.LocalNonce.PubNonce, - } - remoteNonce = &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: ourContribution.RemoteNonce.PubNonce, - } + localNonce = (*lnwire.Musig2Nonce)( + &ourContribution.LocalNonce.PubNonce, + ) } fundingOpen := lnwire.OpenChannel{ @@ -4253,7 +4258,6 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { ChannelType: chanType, LeaseExpiry: leaseExpiry, LocalNonce: localNonce, - RemoteNonce: remoteNonce, } if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil { e := fmt.Errorf("unable to send funding request message: %v", diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 432414c2b0b..e5fbecec569 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -697,17 +697,14 @@ func (l *channelLink) syncChanStates() error { // If this is a tarpoot channel, then in addition to the normal reest // message, we'll also send our local+remote nonces as well. if chanState.ChanType.IsTaproot() { - noncePair, err := l.channel.GenMusigNonces() + localNonce, err := l.channel.GenMusigNonces() if err != nil { return fmt.Errorf("unable to generate nonce "+ "pair for chan: %w", err) } - localChanSyncMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: noncePair.LocalNonce.PubNonce, - } - localChanSyncMsg.RemoteNonce = &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: noncePair.RemoteNonce.PubNonce, - } + localChanSyncMsg.LocalNonce = (*lnwire.Musig2Nonce)( + &localNonce.PubNonce, + ) } if err := l.cfg.Peer.SendMessage(true, localChanSyncMsg); err != nil { @@ -783,15 +780,10 @@ func (l *channelLink) syncChanStates() error { l.log.Infof("initializing musig2 nonces") syncMsg := remoteChanSyncMsg - remoteNonces := &lnwallet.MusigNoncePair{ - LocalNonce: &musig2.Nonces{ - PubNonce: syncMsg.LocalNonce.Musig2Nonce, - }, - RemoteNonce: &musig2.Nonces{ - PubNonce: syncMsg.RemoteNonce.Musig2Nonce, - }, + remoteNonce := &musig2.Nonces{ + PubNonce: *syncMsg.LocalNonce, } - err := l.channel.InitRemoteMusigNonces(remoteNonces) + err := l.channel.InitRemoteMusigNonces(remoteNonce) if err != nil { return fmt.Errorf("unable to init musig2 "+ "nonces: %w", err) @@ -1930,9 +1922,9 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // chain, validate this new commitment, closing the link if // invalid. err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{ - CommitSig: msg.CommitSig, - HtlcSigs: msg.HtlcSigs, - NextSignerNonce: &msg.RemoteNonce.Musig2Nonce, + CommitSig: msg.CommitSig, + HtlcSigs: msg.HtlcSigs, + PartialSig: msg.PartialSig, }) if err != nil { // If we were unable to reconstruct their proposed @@ -2294,12 +2286,10 @@ func (l *channelLink) updateCommitTx() error { } commitSig := &lnwire.CommitSig{ - ChanID: l.ChanID(), - CommitSig: newCommit.CommitSig, - HtlcSigs: newCommit.HtlcSigs, - RemoteNonce: &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: *newCommit.NextSignerNonce, - }, + ChanID: l.ChanID(), + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, } l.cfg.Peer.SendMessage(false, commitSig) diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index a7c2618074a..3dabac2066a 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -316,15 +316,15 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { if err != nil { return nil, err } - shutdown.Musig2Nonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: firstClosingNonce.PubNonce, - } + shutdown.Musig2Nonce = (*lnwire.Musig2Nonce)( + &firstClosingNonce.PubNonce, + ) chancloserLog.Infof("Initiating shutdown w/ nonce: %v", spew.Sdump(firstClosingNonce.PubNonce)) c.musigNoncePair = &lnwallet.MusigNoncePair{ - LocalNonce: firstClosingNonce, + VerificationNonce: *firstClosingNonce, } } @@ -534,8 +534,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // remote nonces so we can properly create a new musig // session for signing. if c.cfg.Channel.ChanType().IsTaproot() { - c.musigNoncePair.RemoteNonce = &musig2.Nonces{ - PubNonce: shutdownMsg.Musig2Nonce.Musig2Nonce, + c.musigNoncePair.SigningNonce = musig2.Nonces{ + PubNonce: *shutdownMsg.Musig2Nonce, } } @@ -601,8 +601,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // local+remote nonces so we can properly create a new musig // session for signing. if c.cfg.Channel.ChanType().IsTaproot() { - c.musigNoncePair.RemoteNonce = &musig2.Nonces{ - PubNonce: shutdownMsg.Musig2Nonce.Musig2Nonce, + c.musigNoncePair.SigningNonce = musig2.Nonces{ + PubNonce: *shutdownMsg.Musig2Nonce, } } @@ -636,18 +636,10 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, "instead have %v", spew.Sdump(msg)) } - // If this is a taproot channel, then the sig will come along - // with a nonce to use for the _next_ proposed state, so we'll - // stash that now. - /*if c.cfg.Channel.ChanType().IsTaproot() { - c.musigNoncePair.RemoteNonce = &musig2.Nonces{ - PubNonce: closeSignedMsg.Musig2Nonce.Musig2Nonce, - } - }*/ - - // We'll compare the proposed total fee, to what we've proposed during - // the negotiations. If it doesn't match any of our prior offers, then - // we'll attempt to ratchet the fee closer to + // We'll compare the proposed total fee, to what we've proposed + // during the negotiations. If it doesn't match any of our + // prior offers, then we'll attempt to ratchet the fee closer + // to remoteProposedFee := closeSignedMsg.FeeSatoshis // For taproot channels, since nonces are involved, we can't do @@ -710,38 +702,45 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+ "negotiation", c.chanPoint, remoteProposedFee) - // Otherwise, we've agreed on a fee for the closing transaction! We'll - // craft the final closing transaction so we can broadcast it to the - // network. - matchingSig := c.priorFeeOffers[remoteProposedFee].Signature - - // At this point, we can complete the final co-op close - // signature. For taproot channels, we'll need to interpret the - // 64 byte signatures as an actual schnorr sig. + // Otherwise, we've agreed on a fee for the closing + // transaction! We'll craft the final closing transaction so we + // can broadcast it to the network. + var ( + localSig, remoteSig input.Signature + closeOpts []lnwallet.ChanCloseOpt + err error + ) + matchingSig := c.priorFeeOffers[remoteProposedFee] if c.cfg.Channel.ChanType().IsTaproot() { - // TODO(roasbeef): tempt ting, need either sig w/ nonce - // or diff type - matchingSig.ForceSchnorr() - closeSignedMsg.Signature.ForceSchnorr() - } - - localSig, err := matchingSig.ToSignature() - if err != nil { - return nil, false, err - } - remoteSig, err := closeSignedMsg.Signature.ToSignature() - if err != nil { - return nil, false, err - } + // We'll convert the wire partial signatures into an + // input.Signature compliant struct so we can pass it + // into the final combination function. + localPartialSig := matchingSig.PartialSig + remotePartialSig := closeSignedMsg.PartialSig + + localSig = new(lnwallet.MusigPartialSig).FromWireSig( + localPartialSig, + ) + remoteSig = new(lnwallet.MusigPartialSig).FromWireSig( + remotePartialSig, + ) - // For taproot channels, we'll need to pass along the session - // so the final combined signature can be created. - var closeOpts []lnwallet.ChanCloseOpt - if c.cfg.Channel.ChanType().IsTaproot() { + // For taproot channels, we'll need to pass along the + // session so the final combined signature can be + // created. closeOpts = append( closeOpts, lnwallet.WithCoopCloseMusigSession(c.musigSession), ) + } else { + localSig, err = matchingSig.Signature.ToSignature() + if err != nil { + return nil, false, err + } + remoteSig, err = closeSignedMsg.Signature.ToSignature() + if err != nil { + return nil, false, err + } } closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose( @@ -753,9 +752,12 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, } c.closingTx = closeTx - // Before publishing the closing tx, we persist it to the database, - // such that it can be republished if something goes wrong. - err = c.cfg.Channel.MarkCoopBroadcasted(closeTx, c.locallyInitiated) + // Before publishing the closing tx, we persist it to the + // database, such that it can be republished if something goes + // wrong. + err = c.cfg.Channel.MarkCoopBroadcasted( + closeTx, c.locallyInitiated, + ) if err != nil { return nil, false, err } @@ -818,10 +820,16 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign // generated for the next co-op close negotiation round. if c.cfg.Channel.ChanType().IsTaproot() { localKey, remoteKey := c.cfg.Channel.MultiSigKeys() - c.musigSession, err = lnwallet.NewMusigSession( - *c.musigNoncePair, localKey, remoteKey, c.cfg.Signer, - c.cfg.Channel.FundingTxOut(), false, + c.musigSession = lnwallet.NewPartialMusigSession( + c.musigNoncePair.VerificationNonce, localKey, remoteKey, + c.cfg.Signer, c.cfg.Channel.FundingTxOut(), false, ) + err := c.musigSession.FinalizeSession( + c.musigNoncePair.SigningNonce, + ) + if err != nil { + return nil, err + } closeOpts = append( closeOpts, @@ -830,7 +838,8 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign } rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal( - fee, c.localDeliveryScript, c.remoteDeliveryScript, closeOpts..., + fee, c.localDeliveryScript, c.remoteDeliveryScript, + closeOpts..., ) if err != nil { return nil, err @@ -841,14 +850,18 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign // We'll note our last signature and proposed fee so when the remote // party responds we'll be able to decide if we've agreed on fees or // not. - var parsedSig lnwire.Sig + var ( + parsedSig lnwire.Sig + partialSig *lnwire.PartialSig + ) if c.cfg.Channel.ChanType().IsTaproot() { - parsedSig, err = lnwire.NewSigFromSchnorrRawSignature( - rawSig.Serialize(), - ) - if err != nil { - return nil, err + musig, ok := rawSig.(*lnwallet.MusigPartialSig) + if !ok { + return nil, fmt.Errorf("expected MusigPartialSig, "+ + "got %T", rawSig) } + + partialSig = musig.ToWireSig() } else { parsedSig, err = lnwire.NewSigFromSignature(rawSig) if err != nil { @@ -856,34 +869,25 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign } } - chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+ - "chan", c.chanPoint, int64(fee)) + chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+ + "close chan", c.chanPoint, int64(fee)) - // We'll assemble a ClosingSigned message using this information and return - // it to the caller so we can kick off the final stage of the channel - // closure process. + // We'll assemble a ClosingSigned message using this information and + // return it to the caller so we can kick off the final stage of the + // channel closure process. closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig) + // For musig2 channels, the main sig is blank, and instead we'll send + // over a partial signature which'll be combine donce our offer is + // accepted. + if partialSig != nil { + closeSignedMsg.PartialSig = partialSig + } + // We'll also save this close signed, in the case that the remote party // accepts our offer. This way, we don't have to re-sign. c.priorFeeOffers[fee] = closeSignedMsg - // Finally, before give the final co-op signed message, we'll generate - // a new local musig nonce. - if c.cfg.Channel.ChanType().IsTaproot() { - localKey, _ := c.cfg.Channel.MultiSigKeys() - c.musigNoncePair.LocalNonce, err = musig2.GenNonces( - musig2.WithPublicKey(localKey.PubKey), - ) - if err != nil { - return nil, err - } - - closeSignedMsg.Musig2Nonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: c.musigNoncePair.LocalNonce.PubNonce, - } - } - return closeSignedMsg, nil } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 698d9620dd6..b136adfc879 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/txsort" @@ -1318,8 +1317,8 @@ type LightningChannel struct { // musigSessions... musigSessions *MusigPairSession - // nextNoncePair... - nextNoncePair *MusigNoncePair + // pendingVerificationNonce... + pendingVerificationNonce *musig2.Nonces // fundingOutput... fundingOutput wire.TxOut @@ -1331,23 +1330,23 @@ type LightningChannel struct { type ChannelOpt func(*channelOpts) // WithLocalMusigNonces... -func WithLocalMusigNonces(nonces *MusigNoncePair) ChannelOpt { +func WithLocalMusigNonces(nonce *musig2.Nonces) ChannelOpt { return func(o *channelOpts) { - o.localNonces = nonces + o.localNonce = nonce } } // WithRemoteMusigNonces... -func WithRemoteMusigNonces(nonces *MusigNoncePair) ChannelOpt { +func WithRemoteMusigNonces(nonces *musig2.Nonces) ChannelOpt { return func(o *channelOpts) { - o.remoteNonces = nonces + o.remoteNonce = nonces } } // channelOpts... type channelOpts struct { - localNonces *MusigNoncePair - remoteNonces *MusigNoncePair + localNonce *musig2.Nonces + remoteNonce *musig2.Nonces } // defaultChannelOpts... @@ -1402,11 +1401,11 @@ func NewLightningChannel(signer input.Signer, // At this point, we mwy already have of nonces that were passed in, so // we'll check that now as this lets us skip some steps later. - if opts.localNonces != nil { - lc.nextNoncePair = opts.localNonces + if opts.localNonce != nil { + lc.pendingVerificationNonce = opts.localNonce } - if lc.nextNoncePair != nil && opts.remoteNonces != nil { - err := lc.InitRemoteMusigNonces(opts.remoteNonces) + if lc.pendingVerificationNonce != nil && opts.remoteNonce != nil { + err := lc.InitRemoteMusigNonces(opts.remoteNonce) if err != nil { return nil, err } @@ -3775,8 +3774,8 @@ type CommitSigs struct { // HtlcSigs... HtlcSigs []lnwire.Sig - // NextSignerNonce... - NextSignerNonce *lnwire.Musig2Nonce + // PartialSig... + PartialSig *lnwire.PartialSig } // NewCommitState wraps the various signatures needed to properly @@ -3814,8 +3813,9 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { } var ( - sig lnwire.Sig - htlcSigs []lnwire.Sig + sig lnwire.Sig + partialSig *lnwire.PartialSig + htlcSigs []lnwire.Sig ) // If we're awaiting for an ACK to a commitment signature, or if we @@ -3905,10 +3905,12 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // the HTLC signatures to be processed. // // TODO(roasbeef): abstract into CommitSigner interface? - var nextSigningNonce *lnwire.Musig2Nonce if lc.channelState.ChanType.IsTaproot() { + // In this case, we'll send out a partial signature as this is + // a musig2 channel. The encoded normal ECDSA signature will be + // just blank. remoteSession := lc.musigSessions.RemoteSession - partialSig, nextNonce, err := remoteSession.SignCommit( + musig, err := remoteSession.SignCommit( newCommitView.txn, ) if err != nil { @@ -3916,15 +3918,7 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { return nil, err } - nextSigningNonce = (*lnwire.Musig2Nonce)(nextNonce) - - // TODO(roasbeef): to just carry nonce w/? - sig, err = lnwire.NewSigFromSchnorrRawSignature( - partialSig.Serialize(), - ) - if err != nil { - return nil, err - } + partialSig = musig.ToWireSig() } else { lc.signDesc.SigHashes = input.NewTxSigHashesV0Only( newCommitView.txn, @@ -3988,9 +3982,9 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { return &NewCommitState{ CommitSigs: &CommitSigs{ - CommitSig: sig, - HtlcSigs: htlcSigs, - NextSignerNonce: nextSigningNonce, + CommitSig: sig, + HtlcSigs: htlcSigs, + PartialSig: partialSig, }, PendingHTLCs: commitDiff.Commitment.Htlcs, }, nil @@ -4163,13 +4157,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( ChanID: lnwire.NewChanIDFromOutPoint( &lc.channelState.FundingOutpoint, ), - CommitSig: newCommit.CommitSig, - HtlcSigs: newCommit.HtlcSigs, - } - if newCommit.NextSignerNonce != nil { - commitSig.RemoteNonce = &lnwire.RemoteMusig2Nonce{ - Musig2Nonce: *newCommit.NextSignerNonce, - } + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, } updates = append(updates, commitSig) @@ -4818,25 +4808,25 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { if lc.channelState.ChanType.IsTaproot() { localSession := lc.musigSessions.LocalSession - // In order to verify the commitment sig, we'll need to craeate - // a proper musig partial sig. - var ( - partialS btcec.ModNScalar - partialSBytes [32]byte + nextVerificationNonce, err := localSession.VerifyCommitSig( + localCommitTx, commitSigs.PartialSig, ) - copy(partialSBytes[:], commitSigs.CommitSig.RawBytes()[32:]) - partialS.SetBytes(&partialSBytes) - - partialSig := musig2.PartialSignature{ - S: &partialS, + if err != nil { + // TODO(roasbeef): new InvalidPartialCommitSigError + return err } - _, err := localSession.VerifyCommitSig( - localCommitTx, &partialSig, + // Now that we have the next verification nonce for our local + // session, we'll refresh it to yield a new session we'll use + // for the next incoming signature. + newLocalSession, err := lc.musigSessions.LocalSession.Refresh( + nextVerificationNonce, ) if err != nil { return err } + lc.musigSessions.LocalSession = newLocalSession + } else { sigHash, err := txscript.CalcWitnessSigHash( multiSigScript, hashCache, txscript.SigHashAll, @@ -4910,22 +4900,6 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() lc.localCommitChain.addCommitment(localCommitmentView) - if !lc.channelState.ChanType.IsTaproot() { - return nil - } - - // At this point, we now have their next signing nonce, so we can - // refresh our local session which allows us to verify more incoming - // commitments. - // - // TODO(roasbef): should isolate here re signer nonce, existing panic - newLocalSession, err := lc.musigSessions.LocalSession.Refresh( - *commitSigs.NextSignerNonce, - ) - if err != nil { - return err - } - lc.musigSessions.LocalSession = newLocalSession return nil } @@ -5104,12 +5078,11 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c // commitment, so we'll send the remote party another verification // nonce they can use to generate new commitments. if lc.channelState.ChanType.IsTaproot() { - musigSession := lc.musigSessions - nextVerificationNonce := musigSession.LocalSession.VerificationNonce() - // TODO(roasbeef): need new method here for verification nonce, in recv sig - revocationMsg.LocalNonce = &lnwire.LocalMusig2Nonce{ - Musig2Nonce: nextVerificationNonce, - } + localSession := lc.musigSessions.LocalSession + nextVerificationNonce := localSession.VerificationNonce() + revocationMsg.LocalNonce = (*lnwire.Musig2Nonce)( + &nextVerificationNonce.PubNonce, + ) } return revocationMsg, newCommitment.Htlcs, nil @@ -5344,7 +5317,9 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( // our remote musig2 session which allows us to create another state. if lc.channelState.ChanType.IsTaproot() { newRemoteSession, err := lc.musigSessions.RemoteSession.Refresh( - revMsg.LocalNonce.Musig2Nonce, + &musig2.Nonces{ + PubNonce: *revMsg.LocalNonce, + }, ) if err != nil { return nil, nil, nil, nil, err @@ -6978,7 +6953,7 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, // channel, so we'll generate a _partial_ signature. var sig input.Signature if opts.musigSession != nil { - sig, _, err = opts.musigSession.SignCommit(closeTx) + sig, err = opts.musigSession.SignCommit(closeTx) if err != nil { return nil, nil, 0, err } @@ -7069,15 +7044,12 @@ func (lc *LightningChannel) CompleteCooperativeClose( // For taproot channels, we'll use the attached session to // combine the two partial signatures into a proper schnorr // signature. - remoteSchnorrSig, _ := remoteSig.(*schnorr.Signature) - - var remotePartialSig MusigPartialSig - remotePartialSig.FromSchnorrShell(remoteSchnorrSig) + remotePartialSig, ok := remoteSig.(*MusigPartialSig) + if !ok { + return nil, 0, fmt.Errorf("expected MusigPartialSig, "+ + "got %T", remoteSig) + } - // TODO(roasbeef): only need one sig combined? - //finalSchnorrSig, err := opts.musigSession.CombineSigs( - //localPartialSig.sig, remotePartialSig.sig, - //) finalSchnorrSig, err := opts.musigSession.CombineSigs( remotePartialSig.sig, ) @@ -7921,41 +7893,30 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex, return localPeerUpdates } -// GenMusigNonces generates the signing and verification nonces to start off a -// new musig2 channel session. -func (lc *LightningChannel) GenMusigNonces() (*MusigNoncePair, error) { +// GenMusigNonces generates the verification nonce to start off a new musig2 +// channel session. +func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { lc.RLock() defer lc.RUnlock() var err error - lc.nextNoncePair, err = NewMusigChannelNonces( + lc.pendingVerificationNonce, err = NewMusigVerificationNonce( lc.channelState.LocalChanCfg.MultiSigKey.PubKey, ) if err != nil { return nil, err } - return lc.nextNoncePair, nil + return lc.pendingVerificationNonce, nil } -// NewMusigChannelNonces... -func NewMusigChannelNonces(pubKey *btcec.PublicKey) (*MusigNoncePair, error) { - pubKeyOpt := musig2.WithPublicKey(pubKey) +// NewMusigVerificationNonce... +func NewMusigVerificationNonce(pubKey *btcec.PublicKey, +) (*musig2.Nonces, error) { - verificationNonce, err := musig2.GenNonces(pubKeyOpt) - if err != nil { - return nil, err - } - - signingNonce, err := musig2.GenNonces(pubKeyOpt) - if err != nil { - return nil, err - } + pubKeyOpt := musig2.WithPublicKey(pubKey) - return &MusigNoncePair{ - LocalNonce: verificationNonce, - RemoteNonce: signingNonce, - }, nil + return musig2.GenNonces(pubKeyOpt) } // HasRemoteNonces returns true if the channel has a remote nonce pair. @@ -7967,44 +7928,35 @@ func (lc *LightningChannel) HasRemoteNonces() bool { // party. This should be called upon connection re-establishment, after we've // generated our own nonces. Once this method returns a nil error, then the // channel can be used to sign commitment states. -func (lc *LightningChannel) InitRemoteMusigNonces(nonces *MusigNoncePair) error { +func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces, +) error { + lc.RLock() defer lc.RUnlock() // Now that we have the set of local and remote nonces, we can generate // a new pair of musig sessions for our local commitment and the // commitment of the remote party. - remoteNonces := nonces - localNonces := lc.nextNoncePair + localNonce := lc.pendingVerificationNonce localChanCfg := lc.channelState.LocalChanCfg remoteChanCfg := lc.channelState.RemoteChanCfg // TODO(roasbeef): propagate rename of signing and verification nonces - var err error sessionCfg := &MusigSessionCfg{ - LocalKey: localChanCfg.MultiSigKey, - RemoteKey: remoteChanCfg.MultiSigKey, - LocalCommitNonces: MusigNoncePair{ - LocalNonce: localNonces.LocalNonce, - RemoteNonce: remoteNonces.RemoteNonce, - }, - RemoteCommitNonces: MusigNoncePair{ - LocalNonce: remoteNonces.LocalNonce, - RemoteNonce: localNonces.RemoteNonce, - }, - Signer: lc.Signer, - InputTxOut: &lc.fundingOutput, - } - lc.musigSessions, err = NewMusigPairSession( + LocalKey: localChanCfg.MultiSigKey, + RemoteKey: remoteChanCfg.MultiSigKey, + LocalNonce: *localNonce, + RemoteNonce: *remoteNonce, + Signer: lc.Signer, + InputTxOut: &lc.fundingOutput, + } + lc.musigSessions = NewMusigPairSession( sessionCfg, ) - if err != nil { - return fmt.Errorf("unable to gen musig session: %w", err) - } - lc.nextNoncePair = nil + lc.pendingVerificationNonce = nil return nil } diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 0189d3e751f..82856bb625b 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -1,6 +1,7 @@ package lnwallet import ( + "bytes" "fmt" "github.com/btcsuite/btcd/btcec/v2" @@ -8,8 +9,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwire" ) // MusigPartialSig... @@ -40,31 +43,33 @@ func NewMusigPartialSig(sig *musig2.PartialSignature, } } -// Serialize serializes the musig2 partial signature. The serializing includes -// the combined nonce _and_ the partial signature. The final signature is -// always 64 bytes in length. -func (p *MusigPartialSig) Serialize() []byte { - var rawSig [schnorr.SignatureSize]byte +// FromWireSig... +func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSig) *MusigPartialSig { + p.sig = &musig2.PartialSignature{ + S: &sig.Sig, + } + p.signerNonce = sig.Nonce - // For the signature, we'll encode only the x-coordinate of the - // combined nonce point. To do this we'll need to convert the R point - // in the sig to jacobian coordinate, and then extract the x-coord from - // that. - // - // TODO(roasbeef): test, or can recompute b, then arrive at the - // combined nonce, given: combinedNonce, combinedKey, msg - var nonceJ btcec.JacobianPoint - p.sig.R.AsJacobian(&nonceJ) - nonceJ.ToAffine() + return p +} - nonceX := &nonceJ.X +// ToWireSig... +func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSig { + return &lnwire.PartialSig{ + Nonce: p.signerNonce, + Sig: *p.sig.S, + } +} - nonceX.PutBytesUnchecked(rawSig[:]) - p.sig.S.PutBytesUnchecked(rawSig[32:]) +// Serialize serializes the musig2 partial signature. The serializing includes +// the signer's public nonce _and_ the partial signature. The final signature +// is always 98 bytes in length. +func (p *MusigPartialSig) Serialize() []byte { + var b bytes.Buffer - // TODO(roasbeef): update to 98 byte serialization? + p.ToWireSig().Encode(&b) - return rawSig[:] + return b.Bytes() } // ToSchnorrShell... @@ -96,8 +101,6 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { var m [32]byte copy(m[:], msg) - // TODO(roasbeef): need diff nonce here?? - return p.sig.Verify( p.signerNonce, p.combinedNonce, p.signerKeys, pub, m, musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), @@ -105,22 +108,19 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { } // MusigNoncePair... -// -// TODO(roasbeef): rename to nonce1 and nonce2? -// - or signing nonce and verification nonce type MusigNoncePair struct { - // LocalNonce... - LocalNonce *musig2.Nonces + // SigningNonce... + SigningNonce musig2.Nonces - // RemoteNonce... - RemoteNonce *musig2.Nonces + // VerificationNonce... + VerificationNonce musig2.Nonces } // String... func (n *MusigNoncePair) String() string { return fmt.Sprintf("NoncePair(verification_nonce=%x, "+ - "signing_nonce=%x)", n.LocalNonce.PubNonce[:], - n.RemoteNonce.PubNonce[:]) + "signing_nonce=%x)", n.VerificationNonce.PubNonce[:], + n.SigningNonce.PubNonce[:]) } // MusigSession... @@ -133,10 +133,6 @@ type MusigSession struct { // next commitment. nonces MusigNoncePair - // nextNonces is the next set of nonces to start using once a - // revocation or new state occurs. - nextNonces *MusigNoncePair - // inputTxOut... inputTxOut *wire.TxOut @@ -156,64 +152,86 @@ type MusigSession struct { remoteCommit bool } -// NewMusigSession... -func NewMusigSession(noncePair MusigNoncePair, +// NewPartialMusigSession... +func NewPartialMusigSession(verificationNonce musig2.Nonces, localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer, inputTxOut *wire.TxOut, - remoteCommit bool) (*MusigSession, error) { + remoteCommit bool) *MusigSession { - var localNonce, remoteNonce *musig2.Nonces + signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} + + nonces := MusigNoncePair{ + VerificationNonce: verificationNonce, + } + + return &MusigSession{ + nonces: nonces, + remoteKey: remoteKey, + localKey: localKey, + inputTxOut: inputTxOut, + signerKeys: signerKeys, + signer: signer, + remoteCommit: remoteCommit, + } +} + +// finalizeSession... +// +// TODO(roasbeef): make private again, add NewMusigSessionthat calls above then +// calls thisd +func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error { + var ( + localNonce, remoteNonce musig2.Nonces + err error + ) + + // First, we'll stash the freshly generated signing nonce. Depending on + // who's commitment we're handling, this'll either be our generated + // nonce, or the one we just got from the remote party. + m.nonces.SigningNonce = signingNonce - // If we're making a session for the remote commitment, then the nonce - // we use to sign is actually our _remote_ nonce, and their - // verification nonce is the local nonce. switch { - case remoteCommit: - localNonce = noncePair.RemoteNonce - remoteNonce = noncePair.LocalNonce - // Otherwise, we're generating a signature for our local commitment (to - // broadcast), so we'll use our normal local nonce for signing. + // If we're making a session for the remote commitment, then the nonce + // we use to sign is actually will be the signing nonce for the + // session, and their nonce the verification nonce. + case m.remoteCommit: + localNonce = m.nonces.SigningNonce + remoteNonce = m.nonces.VerificationNonce + + // Otherwise, we're generating/receiving a signature for our local + // commitment (to broadcast), so now our verification nonce is the one + // we've already generated, and we want to bind their new signing + // nonce. default: - localNonce = noncePair.LocalNonce - remoteNonce = noncePair.RemoteNonce + localNonce = m.nonces.VerificationNonce + remoteNonce = m.nonces.SigningNonce } - signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} tweakDesc := input.MuSig2Tweaks{ TaprootBIP0086Tweak: true, } - session, err := signer.MuSig2CreateSession( - localKey.KeyLocator, signerKeys, &tweakDesc, + m.session, err = m.signer.MuSig2CreateSession( + m.localKey.KeyLocator, m.signerKeys, &tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, - musig2.WithPreGeneratedNonce(localNonce), + musig2.WithPreGeneratedNonce(&localNonce), ) if err != nil { - return nil, err + return err } // We'll need the raw combined nonces later to be able to verify // partial signatures, and also combine partial signatures, so we'll // generate it now ourselves. - combinedNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{ - noncePair.LocalNonce.PubNonce, - noncePair.RemoteNonce.PubNonce, + m.combinedNonce, err = musig2.AggregateNonces([][musig2.PubNonceSize]byte{ + m.nonces.SigningNonce.PubNonce, + m.nonces.VerificationNonce.PubNonce, }) if err != nil { - return nil, err + return nil } - return &MusigSession{ - nonces: noncePair, - remoteKey: remoteKey, - localKey: localKey, - session: session, - combinedNonce: combinedNonce, - inputTxOut: inputTxOut, - signerKeys: signerKeys, - signer: signer, - remoteCommit: remoteCommit, - }, nil + return nil } // taprootKeyspendSighash... @@ -234,16 +252,40 @@ func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte, // SignCommit signs the passed commitment w/ the current signing (relative // remote) nonce. Given nonces should only ever be used once, once the method // returns a new nonce is returned, w/ the existing nonce blanked out. -func (m *MusigSession) SignCommit(tx *wire.MsgTx, -) (*MusigPartialSig, *[musig2.PubNonceSize]byte, error) { +func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) { + // If we already have a session, then we don't need to finalize as this + // was done up front (symmetric nonce case, like for co-op close). + if m.session == nil { + // Before we can sign a new commitment, we'll need to generate + // a fresh nonce that'll be sent along side our signature. With + // the nonce in hand, we can finalize the session. + // + // TODO(roasbeef): can also pass in stuff like the sighash to + // further bind context, etc, etc. + signingNonce, err := musig2.GenNonces( + musig2.WithPublicKey(m.localKey.PubKey), + ) + if err != nil { + return nil, err + } + if err := m.FinalizeSession(*signingNonce); err != nil { + return nil, err + } + } - // Before we can sign, we'll need to generate the sighash for their + // Once we sign with a nonce, we'll never use it again, so it's safe to + // go ahead and clean up the session right here. + // defer m.signer.MuSig2Cleanup(m.session.SessionID) + // + // TODO(roasbeef): can't clean up here as need to combine sig + + // Next we can sign, we'll need to generate the sighash for their // commitment transaction. sigHash, err := taprootKeyspendSighash( tx, m.inputTxOut.PkScript, m.inputTxOut.Value, ) if err != nil { - return nil, nil, err + return nil, err } // Now that we have our session created, we'll use it to generate the @@ -258,80 +300,33 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx, m.session.SessionID, sigHashMsg, false, ) if err != nil { - return nil, nil, err - } - - // Now that we've generated a signature with this nonce, we'll generate - // another nonce for the _next_ commitment. This'll go in the set of - // nonces for the next state, as we still need the remote party's - // verification nonce (their relative local nonce). - nextSigningNonce, err := musig2.GenNonces( - musig2.WithPublicKey(m.localKey.PubKey), - ) - if err != nil { - return nil, nil, fmt.Errorf("unable to gen new nonce: %w", err) - } - - var nextNonces MusigNoncePair - switch { - case m.remoteCommit: - nextNonces.RemoteNonce = nextSigningNonce - default: - nextNonces.LocalNonce = nextSigningNonce + return nil, err } - m.nextNonces = &nextNonces - - // TODO(roasbeef): clean up prior session once new created? - return NewMusigPartialSig( sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, - ), &nextSigningNonce.PubNonce, nil + ), nil } -// Refresh... -func (m *MusigSession) Refresh(nextNonce [musig2.PubNonceSize]byte) (*MusigSession, error) { - // At this point we should have a next nonce, otherwise this operation - // is undefined as we haven't yet used our current nonce. - if m.nextNonces == nil { - // TODO(roasbeef): proper error - return nil, fmt.Errorf("no next nonce") - } +// Refresh is called once we receive a new verification nonce from the remote +// party after sending a signature. This nonce will be coupled within the +// revoke-and-ack message of the remote party. +func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces, +) (*MusigSession, error) { - // Now that we know we have the nonce we need, we can complete the - // nonce pair. - if m.remoteCommit { - m.nextNonces.LocalNonce = &musig2.Nonces{ - PubNonce: nextNonce, - } - } else { - m.nextNonces.RemoteNonce = &musig2.Nonces{ - PubNonce: nextNonce, - } - } + // TODO(roasbeef): need to also pass along verification nonce + // * local commit: called after recv'ing a sig + // * remote commit: called when get revoke-and-ack - // Now we'll just re-create ourselves entirely given this new - // information. We'll also clean up the old session since we don't need - // it any longer. - // - // TODO(roasbeef): can't actually clean up here? but need the stateless - // signer thing? - oldSessionID := m.session.SessionID - defer m.signer.MuSig2Cleanup(oldSessionID) - - return NewMusigSession( - *m.nextNonces, m.localKey, m.remoteKey, m.signer, m.inputTxOut, - m.remoteCommit, - ) + return NewPartialMusigSession( + *verificationNonce, m.localKey, m.remoteKey, m.signer, + m.inputTxOut, m.remoteCommit, + ), nil } -// VerificationNonce... -func (m *MusigSession) VerificationNonce() [musig2.PubNonceSize]byte { - if m.remoteCommit { - return m.nonces.RemoteNonce.PubNonce - } else { - return m.nonces.LocalNonce.PubNonce - } +// VerificationNonce returns the current verification nonce for the session. +func (m *MusigSession) VerificationNonce() *musig2.Nonces { + return &m.nonces.VerificationNonce } // TODO(roasbeef): re hot signatures, maybe would re-use the state less signing @@ -346,16 +341,26 @@ func (m *MusigSession) VerificationNonce() [musig2.PubNonceSize]byte { // relative local nonce) returned to transmit to the remote party, which allows // them to generate another signature. func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, - sig *musig2.PartialSignature) (*[musig2.PubNonceSize]byte, error) { + sig *lnwire.PartialSig) (*musig2.Nonces, error) { + + // Before we can verify the signature, we'll need to finalize the + // session by binding the remote party's provided signing nonce. + if err := m.FinalizeSession(musig2.Nonces{ + PubNonce: sig.Nonce, + }); err != nil { + return nil, err + } // When we verify a commitment signature, we always assume that we're // verifying a signature on our local commitment. Therefore, we'll use: // their remote nonce, and also public key. partialSig := NewMusigPartialSig( - sig, m.nonces.RemoteNonce.PubNonce, m.combinedNonce, - m.signerKeys, + &musig2.PartialSignature{S: &sig.Sig}, + m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys, ) + walletLog.Infof("verify partial sig: %v", spew.Sdump(partialSig)) + // With the partial sig loaded with the proper context, we'll now // generate the sighash that the remote party should have signed. sigHash, err := taprootKeyspendSighash( @@ -375,8 +380,6 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, // At this point, we know that their signature is valid, so we'll // generate another verification nonce for them, so they can generate a // new state transition. - // - // TODO(roasbeef): do this conditionally? nextVerificationNonce, err := musig2.GenNonces( musig2.WithPublicKey(m.localKey.PubKey), ) @@ -384,11 +387,7 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, return nil, fmt.Errorf("unable to gen new nonce: %w", err) } - m.nextNonces = &MusigNoncePair{ - LocalNonce: nextVerificationNonce, - } - - return &nextVerificationNonce.PubNonce, nil + return nextVerificationNonce, nil } // CombineSigs... @@ -414,11 +413,11 @@ type MusigSessionCfg struct { // RemoteKey... RemoteKey keychain.KeyDescriptor - // LocalCommitNonces... - LocalCommitNonces MusigNoncePair + // LocalNonce... + LocalNonce musig2.Nonces - // RemoteCommitNonces... - RemoteCommitNonces MusigNoncePair + // RemoteNonce... + RemoteNonce musig2.Nonces // Signer... Signer input.MuSig2Signer @@ -447,33 +446,26 @@ type MusigPairSession struct { // TODO(roasbeef): move sig here? // NewMusigPairSession.... -func NewMusigPairSession(cfg *MusigSessionCfg) (*MusigPairSession, error) { +func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession { // Given the config passed in, we'll now create our two sessions: one // for the local commit, and one for the remote commit. // - // The session for the local commit uses our local nonce and the remote - // party's remote nonce. The session for the remote commit uses our - // remote nonces, and the remote party's local nonce. - localSession, err := NewMusigSession( - cfg.LocalCommitNonces, cfg.LocalKey, cfg.RemoteKey, + // Both sessions will be created using only the verification nonce for + // the local+remote party. + localSession := NewPartialMusigSession( + cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer, cfg.InputTxOut, false, ) - if err != nil { - return nil, err - } - remoteSession, err := NewMusigSession( - cfg.RemoteCommitNonces, cfg.LocalKey, cfg.RemoteKey, + remoteSession := NewPartialMusigSession( + cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer, cfg.InputTxOut, true, ) - if err != nil { - return nil, err - } return &MusigPairSession{ LocalSession: localSession, RemoteSession: remoteSession, signer: cfg.Signer, - }, nil + } } // TODO(roasbeef): chan reest has a late nonce binding diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 89f5f29671b..5515a65a20f 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -137,11 +137,6 @@ type ChannelContribution struct { // channel. This stores the public (and secret) nonce that will be used // to generate commitments for the local party. LocalNonce *musig2.Nonces - - // RemoteNonce is populated if the channel type is a simple taproot - // channel. This stores the public (and secret) nonce that will be used - // to generate commitments for the remote party. - RemoteNonce *musig2.Nonces } // toChanConfig returns the raw channel configuration generated by a node's diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index e077e621b36..2424757f937 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -5,7 +5,6 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" @@ -114,8 +113,6 @@ type SignJobResp struct { Err error } -// TODO(roasbeef); fix description - // SigPool is a struct that is meant to allow the current channel state // machine to parallelize all signature generation and verification. This // struct is needed as _each_ HTLC when creating a commitment transaction @@ -207,7 +204,11 @@ func (s *SigPool) poolWorker() { } } + // Use the sig mapper to go from the input.Signature + // into the serialized lnwire.Sig that we'll send + // across the wire. sig, err := lnwire.NewSigFromSignature(rawSig) + select { case sigMsg.Resp <- SignJobResp{ Sig: sig, diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 41b1df4b3a7..59fd06a3203 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" @@ -1222,12 +1221,6 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation, if err != nil { return err } - reservation.ourContribution.RemoteNonce, err = musig2.GenNonces( - pubKeyOpt, - ) - if err != nil { - return err - } } return nil @@ -1492,30 +1485,16 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { // genMusigSession... func genMusigSession(ourContribution, theirContribution *ChannelContribution, signer input.MuSig2Signer, - fundingOutput *wire.TxOut) (*MusigPairSession, error) { - - sessionCfg := &MusigSessionCfg{ - LocalKey: ourContribution.MultiSigKey, - RemoteKey: theirContribution.MultiSigKey, - LocalCommitNonces: MusigNoncePair{ - LocalNonce: ourContribution.LocalNonce, - RemoteNonce: theirContribution.RemoteNonce, - }, - RemoteCommitNonces: MusigNoncePair{ - LocalNonce: theirContribution.LocalNonce, - RemoteNonce: ourContribution.RemoteNonce, - }, - Signer: signer, - InputTxOut: fundingOutput, - } - musigSessions, err := NewMusigPairSession( - sessionCfg, - ) - if err != nil { - return nil, fmt.Errorf("unable to gen musig session: %w", err) - } - - return musigSessions, nil + fundingOutput *wire.TxOut) *MusigPairSession { + + return NewMusigPairSession(&MusigSessionCfg{ + LocalKey: ourContribution.MultiSigKey, + RemoteKey: theirContribution.MultiSigKey, + LocalNonce: *ourContribution.LocalNonce, + RemoteNonce: *theirContribution.LocalNonce, + Signer: signer, + InputTxOut: fundingOutput, + }) } // signCommitTx... @@ -1537,14 +1516,10 @@ func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, // We're now ready to sign the first commitment. However, we'll // only create the session if that hasn't been done already. if pendingReservation.musigSessions == nil { - musigSessions, err := genMusigSession( - ourContribution, theirContribution, l.Cfg.Signer, - fundingOutput, + musigSessions := genMusigSession( + ourContribution, theirContribution, + l.Cfg.Signer, fundingOutput, ) - if err != nil { - return nil, err - } - pendingReservation.musigSessions = musigSessions } @@ -1556,7 +1531,7 @@ func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, // TODO(roasbeef): keep the signing nonce here? or just always // regen for funding_locked? musigSessions := pendingReservation.musigSessions - partialSig, _, err := musigSessions.RemoteSession.SignCommit( + partialSig, err := musigSessions.RemoteSession.SignCommit( commitTx, ) if err != nil { @@ -1564,7 +1539,7 @@ func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, "commitment: %w", err) } - sigTheirCommit = partialSig.ToSchnorrShell() + sigTheirCommit = partialSig // For regular channels, we can just send over a normal ECDSA signature // w/o any extra steps. @@ -1894,7 +1869,6 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, // p2wsh sighash. switch { case !res.partialState.ChanType.IsTaproot(): - hashCache := input.NewTxSigHashesV0Only(commitTx) witnessScript, _, err := input.GenFundingPkScript( localKey.SerializeCompressed(), @@ -1935,13 +1909,10 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, return err } - res.musigSessions, err = genMusigSession( + res.musigSessions = genMusigSession( res.ourContribution, res.theirContribution, l.Cfg.Signer, fundingOutput, ) - if err != nil { - return err - } } // For the musig2 based channels, we'll use the generated local @@ -1951,27 +1922,16 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, // At this point, the commitment signature passed in should // actually be a wrapped musig2 signature, so we'll do a type // asset to the get the signature we actually need. - switch partialSig := commitSig.(type) { - /*case *MusigPartialSig: - _, err := localSession.VerifyCommitSig( - commitTx, partialSig.sig, - ) - return err*/ - - // TODO(roasbeef): delete and go w/ sig in nonce, temp - case *schnorr.Signature: - remoteSig := new(MusigPartialSig) - remoteSig.FromSchnorrShell(partialSig) - - _, err := localSession.VerifyCommitSig( - commitTx, remoteSig.sig, - ) - return err - - default: + partialSig, ok := commitSig.(*MusigPartialSig) + if !ok { return fmt.Errorf("expected *musig2.PartialSignature, "+ "got: %T", commitSig) } + + _, err := localSession.VerifyCommitSig( + commitTx, partialSig.ToWireSig(), + ) + return err } } diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index 16fe027889a..66dda815c61 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -105,15 +105,12 @@ type AcceptChannel struct { // type. LeaseExpiry *LeaseExpiry - // LocalNonce is an optional field that stores a local musig2 nonce. + // LocalNonce is an optional field that transmits the + // local/verification nonce for a party. This nonce will be used to + // verify the very first commitment transaction signature. // This will only be populated if the simple taproot channels type was // negotiated. - LocalNonce *LocalMusig2Nonce - - // RemoteNoncee is an optional field that stores a remote musig2 nonce. - // This will only be populated if the simple taproot channels type was - // negotiated. - RemoteNonce *RemoteMusig2Nonce + LocalNonce *Musig2Nonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -147,9 +144,6 @@ func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error { if a.LocalNonce != nil { recordProducers = append(recordProducers, a.LocalNonce) } - if a.RemoteNonce != nil { - recordProducers = append(recordProducers, a.RemoteNonce) - } err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err @@ -254,12 +248,11 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { var ( chanType ChannelType leaseExpiry LeaseExpiry - localNonce LocalMusig2Nonce - remoteNonce RemoteMusig2Nonce + localNonce Musig2Nonce ) typeMap, err := tlvRecords.ExtractRecords( &a.UpfrontShutdownScript, &chanType, &leaseExpiry, - &localNonce, &remoteNonce, + &localNonce, ) if err != nil { return err @@ -272,12 +265,9 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { a.LeaseExpiry = &leaseExpiry } - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + if val, ok := typeMap[NonceRecordType]; ok && val == nil { a.LocalNonce = &localNonce } - if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { - a.RemoteNonce = &remoteNonce - } a.ExtraData = tlvRecords diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go index 78819f39d1f..77bf5e9ee4b 100644 --- a/lnwire/channel_reestablish.go +++ b/lnwire/channel_reestablish.go @@ -66,12 +66,9 @@ type ChannelReestablish struct { // LocalNonce is an optional field that stores a local musig2 nonce. // This will only be populated if the simple taproot channels type was // negotiated. - LocalNonce *LocalMusig2Nonce - - // RemoteNonce is an optional field that stores a remote musig2 nonce. - // This will only be populated if the simple taproot channels type was - // negotiated. - RemoteNonce *RemoteMusig2Nonce + // + // TODO(roasbeef): rename to verification nonce + LocalNonce *Musig2Nonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -124,9 +121,6 @@ func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error { if a.LocalNonce != nil { recordProducers = append(recordProducers, a.LocalNonce) } - if a.RemoteNonce != nil { - recordProducers = append(recordProducers, a.RemoteNonce) - } err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err @@ -185,23 +179,17 @@ func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error { return err } - var ( - localNonce LocalMusig2Nonce - remoteNonce RemoteMusig2Nonce - ) + var localNonce Musig2Nonce typeMap, err := tlvRecords.ExtractRecords( - &localNonce, &remoteNonce, + &localNonce, ) if err != nil { return err } - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + if val, ok := typeMap[NonceRecordType]; ok && val == nil { a.LocalNonce = &localNonce } - if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { - a.RemoteNonce = &remoteNonce - } if len(tlvRecords) != 0 { a.ExtraData = tlvRecords diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index a387f0358ff..fb4ba1811e7 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -28,13 +28,16 @@ type ClosingSigned struct { FeeSatoshis btcutil.Amount // Signature is for the proposed channel close transaction. - // - // TODO(roasbeef): need another sig type? Signature Sig - // Musig2Nonce is the nonce the sender will use to sign the first co-op - // sign offer. - Musig2Nonce *LocalMusig2Nonce + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSig + + // TODO(roasbef): ^ make into diff type // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -72,16 +75,16 @@ func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { } var ( - musigNonce LocalMusig2Nonce + partialSig PartialSig ) - typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) if err != nil { return err } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { - c.Musig2Nonce = &musigNonce + if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + c.PartialSig = &partialSig } if len(tlvRecords) != 0 { @@ -97,8 +100,8 @@ func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { // This is part of the lnwire.Message interface. func (c *ClosingSigned) Encode(w *bytes.Buffer, pver uint32) error { recordProducers := make([]tlv.RecordProducer, 0, 1) - if c.Musig2Nonce != nil { - recordProducers = append(recordProducers, c.Musig2Nonce) + if c.PartialSig != nil { + recordProducers = append(recordProducers, c.PartialSig) } err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) if err != nil { diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go index 01c9ae3dbf1..5a9af1fa236 100644 --- a/lnwire/commit_sig.go +++ b/lnwire/commit_sig.go @@ -38,13 +38,12 @@ type CommitSig struct { // transaction should be signed. HtlcSigs []Sig - // RemoteNnoce is the "remote" nonce of the sending party, which the - // sender will use to generate a new commitment party after a - // revocation message has been sent. + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. // - // NOTE: This field is only populated if simple taproot channels are in - // use. - RemoteNonce *RemoteMusig2Nonce + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSig // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -83,16 +82,16 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { } var ( - musigNonce RemoteMusig2Nonce + partialSig PartialSig ) - typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) if err != nil { return err } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { - c.RemoteNonce = &musigNonce + if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + c.PartialSig = &partialSig } if len(tlvRecords) != 0 { @@ -108,8 +107,8 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { // This is part of the lnwire.Message interface. func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error { recordProducers := make([]tlv.RecordProducer, 0, 1) - if c.RemoteNonce != nil { - recordProducers = append(recordProducers, c.RemoteNonce) + if c.PartialSig != nil { + recordProducers = append(recordProducers, c.PartialSig) } err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) if err != nil { diff --git a/lnwire/funding_created.go b/lnwire/funding_created.go index 02b5134716e..b7b706a67dd 100644 --- a/lnwire/funding_created.go +++ b/lnwire/funding_created.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/tlv" ) // FundingCreated is sent from Alice (the initiator) to Bob (the responder), @@ -26,6 +27,13 @@ type FundingCreated struct { // transaction. CommitSig Sig + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSig + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -42,6 +50,15 @@ var _ Message = (*FundingCreated)(nil) // // This is part of the lnwire.Message interface. func (f *FundingCreated) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if f.PartialSig != nil { + recordProducers = append(recordProducers, f.PartialSig) + } + err := EncodeMessageExtraData(&f.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteBytes(w, f.PendingChannelID[:]); err != nil { return err } @@ -63,10 +80,36 @@ func (f *FundingCreated) Encode(w *bytes.Buffer, pver uint32) error { // // This is part of the lnwire.Message interface. func (f *FundingCreated) Decode(r io.Reader, pver uint32) error { - return ReadElements( + err := ReadElements( r, f.PendingChannelID[:], &f.FundingPoint, &f.CommitSig, - &f.ExtraData, ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + partialSig PartialSig + ) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + f.PartialSig = &partialSig + } + + if len(tlvRecords) != 0 { + f.ExtraData = tlvRecords + } + + return nil } // MsgType returns the uint32 code which uniquely identifies this message as a diff --git a/lnwire/funding_locked.go b/lnwire/funding_locked.go index 776ff7295ba..60143248396 100644 --- a/lnwire/funding_locked.go +++ b/lnwire/funding_locked.go @@ -27,15 +27,10 @@ type FundingLocked struct { // ShortChannelID for forwarding. AliasScid *ShortChannelID - // LocalNonce is an optional field that stores a local musig2 nonce. - // This will only be populated if the simple taproot channels type was + // LocalNonce is an optional field that stores a local musig2 nonce. This + // will only be populated if the simple taproot channels type was // negotiated. - LocalNonce *LocalMusig2Nonce - - // RemoteNonce is an optional field that stores a remote musig2 nonce. - // This will only be populated if the simple taproot channels type was - // negotiated. - RemoteNonce *RemoteMusig2Nonce + LocalNonce *Musig2Nonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -80,12 +75,11 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { // Next we'll parse out the set of known records. For now, this is just // the AliasScidRecordType. var ( - aliasScid ShortChannelID - localNonce LocalMusig2Nonce - remoteNonce RemoteMusig2Nonce + aliasScid ShortChannelID + localNonce Musig2Nonce ) typeMap, err := tlvRecords.ExtractRecords( - &aliasScid, &localNonce, &remoteNonce, + &aliasScid, &localNonce, ) if err != nil { return err @@ -96,12 +90,9 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[AliasScidRecordType]; ok && val == nil { c.AliasScid = &aliasScid } - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + if val, ok := typeMap[NonceRecordType]; ok && val == nil { c.LocalNonce = &localNonce } - if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { - c.RemoteNonce = &remoteNonce - } if len(tlvRecords) != 0 { c.ExtraData = tlvRecords @@ -132,9 +123,6 @@ func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error { if c.LocalNonce != nil { recordProducers = append(recordProducers, c.LocalNonce) } - if c.RemoteNonce != nil { - recordProducers = append(recordProducers, c.RemoteNonce) - } err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) if err != nil { return err diff --git a/lnwire/funding_signed.go b/lnwire/funding_signed.go index 5af5f0d5ca5..89a6ddde9e9 100644 --- a/lnwire/funding_signed.go +++ b/lnwire/funding_signed.go @@ -3,6 +3,8 @@ package lnwire import ( "bytes" "io" + + "github.com/lightningnetwork/lnd/tlv" ) // FundingSigned is sent from Bob (the responder) to Alice (the initiator) @@ -15,12 +17,15 @@ type FundingSigned struct { // CommitSig is Bob's signature for Alice's version of the commitment // transaction. - // - // TODO(roasbeef): schnorr sigs have a diff encoding... - // * need to make this a type param instead? - // * or interface to wrap the structure? CommitSig Sig + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSig + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -37,6 +42,15 @@ var _ Message = (*FundingSigned)(nil) // // This is part of the lnwire.Message interface. func (f *FundingSigned) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if f.PartialSig != nil { + recordProducers = append(recordProducers, f.PartialSig) + } + err := EncodeMessageExtraData(&f.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, f.ChanID); err != nil { return err } @@ -54,7 +68,34 @@ func (f *FundingSigned) Encode(w *bytes.Buffer, pver uint32) error { // // This is part of the lnwire.Message interface. func (f *FundingSigned) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, &f.ChanID, &f.CommitSig, &f.ExtraData) + err := ReadElements(r, &f.ChanID, &f.CommitSig) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + partialSig PartialSig + ) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + f.PartialSig = &partialSig + } + + if len(tlvRecords) != 0 { + f.ExtraData = tlvRecords + } + + return nil } // MsgType returns the uint32 code which uniquely identifies this message as a diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index ff098d50c20..565960d075c 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "fmt" "image/color" "io" "math" @@ -40,18 +41,26 @@ var ( const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -func randLocalNonce(r *rand.Rand) *LocalMusig2Nonce { - var nonce LocalMusig2Nonce - _, _ = io.ReadFull(r, nonce.Musig2Nonce[:]) +func randLocalNonce(r *rand.Rand) *Musig2Nonce { + var nonce Musig2Nonce + _, _ = io.ReadFull(r, nonce[:]) return &nonce } -func randRemoteNonce(r *rand.Rand) *RemoteMusig2Nonce { - var nonce RemoteMusig2Nonce - _, _ = io.ReadFull(r, nonce.Musig2Nonce[:]) +func randPartialSig(r *rand.Rand) (*PartialSig, error) { + var sigBytes [32]byte + if _, err := r.Read(sigBytes[:]); err != nil { + return nil, fmt.Errorf("unable to generate sig: %v", err) + } - return &nonce + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[:]) + + return &PartialSig{ + Sig: s, + Nonce: *randLocalNonce(r), + }, nil } func randAlias(r *rand.Rand) NodeAlias { @@ -455,7 +464,6 @@ func TestLightningWireProtocol(t *testing.T) { *req.LeaseExpiry = LeaseExpiry(1337) req.LocalNonce = randLocalNonce(r) - req.RemoteNonce = randRemoteNonce(r) } else { req.UpfrontShutdownScript = []byte{} } @@ -530,7 +538,6 @@ func TestLightningWireProtocol(t *testing.T) { *req.LeaseExpiry = LeaseExpiry(1337) req.LocalNonce = randLocalNonce(r) - req.RemoteNonce = randRemoteNonce(r) } else { req.UpfrontShutdownScript = []byte{} } @@ -565,6 +572,15 @@ func TestLightningWireProtocol(t *testing.T) { return } + // 1/2 chance to attach a partial sig. + if r.Intn(2) == 0 { + req.PartialSig, err = randPartialSig(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgFundingSigned: func(v []reflect.Value, r *rand.Rand) { @@ -585,6 +601,15 @@ func TestLightningWireProtocol(t *testing.T) { return } + // 1/2 chance to attach a partial sig. + if r.Intn(2) == 0 { + req.PartialSig, err = randPartialSig(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgFundingLocked: func(v []reflect.Value, r *rand.Rand) { @@ -607,7 +632,6 @@ func TestLightningWireProtocol(t *testing.T) { scid := NewShortChanIDFromInt(uint64(r.Int63())) req.AliasScid = &scid req.LocalNonce = randLocalNonce(r) - req.RemoteNonce = randRemoteNonce(r) } v[0] = reflect.ValueOf(*req) @@ -656,7 +680,11 @@ func TestLightningWireProtocol(t *testing.T) { } if r.Int31()%2 == 0 { - req.Musig2Nonce = randLocalNonce(r) + req.PartialSig, err = randPartialSig(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", err) + return + } } v[0] = reflect.ValueOf(req) @@ -691,9 +719,13 @@ func TestLightningWireProtocol(t *testing.T) { } } - // 50/50 chance to attach a remote nonce. + // 50/50 chance to attach a partial sig. if r.Int31()%2 == 0 { - req.RemoteNonce = randRemoteNonce(r) + req.PartialSig, err = randPartialSig(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", err) + return + } } v[0] = reflect.ValueOf(*req) @@ -942,7 +974,6 @@ func TestLightningWireProtocol(t *testing.T) { } req.LocalNonce = randLocalNonce(r) - req.RemoteNonce = randRemoteNonce(r) } v[0] = reflect.ValueOf(req) diff --git a/lnwire/musig2.go b/lnwire/musig2.go index b6a60b0449c..34a9bc9ef72 100644 --- a/lnwire/musig2.go +++ b/lnwire/musig2.go @@ -1,7 +1,6 @@ package lnwire import ( - "fmt" "io" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" @@ -9,12 +8,8 @@ import ( ) const ( - // LocalNonceRecordType is the TLV type used to encode a local musig2 nonce. - LocalNonceRecordType = 2 - - // RemoteNonceRecordType is the TLV type used to encode a remote musig2 - // nonce. - RemoteNonceRecordType = 4 + // NonceRecordType is the TLV type used to encode a local musig2 nonce. + NonceRecordType = 4 ) // Musig2Nonce represents a musig2 public nonce, which is the concatenation of @@ -23,9 +18,9 @@ type Musig2Nonce [musig2.PubNonceSize]byte // Record returns a TLV record that can be used to encode/decode the musig2 // nonce from a given TLV stream. -func (m *Musig2Nonce) record(nonceType tlv.Type) tlv.Record { +func (m *Musig2Nonce) Record() tlv.Record { return tlv.MakeStaticRecord( - nonceType, m, musig2.PubNonceSize, nonceTypeEncoder, + NonceRecordType, m, musig2.PubNonceSize, nonceTypeEncoder, nonceTypeDecoder, ) } @@ -37,12 +32,13 @@ func nonceTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { return err } - fmt.Println("kek") return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") } // nonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. -func nonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { +func nonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + if v, ok := val.(*Musig2Nonce); ok { _, err := io.ReadFull(r, v[:]) return err @@ -52,27 +48,3 @@ func nonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) erro val, "lnwire.Musig2Nonce", l, musig2.PubNonceSize, ) } - -// LocalMusig2Nonce is a wrapper type around the Musig2Nonce type that adds the -// directional context of a "local" nonce. -// -// TODO(roasbeef): instead have single struct? -type LocalMusig2Nonce struct { - Musig2Nonce -} - -// Record returns the record to be used for encoding a local musig2 nonce. -func (l *LocalMusig2Nonce) Record() tlv.Record { - return l.record(LocalNonceRecordType) -} - -// RemoteMusig2Nonce is a wrapper type around the Musig2Nonce type that adds -// the directional context of a "remote" nonce. -type RemoteMusig2Nonce struct { - Musig2Nonce -} - -// Record returns the record to be used for encoding a remote musig2 nonce. -func (r *RemoteMusig2Nonce) Record() tlv.Record { - return r.record(RemoteNonceRecordType) -} diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 4308eb805fc..9cb4bc41ad3 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -141,15 +141,12 @@ type OpenChannel struct { // type. LeaseExpiry *LeaseExpiry - // LocalNonce is an optional field that stores a local musig2 nonce. - // This will only be populated if the simple taproot channels type was + // LocalNonce is an optional field that transmits the + // local/verification nonce for a party. This nonce will be used to + // verify the very first commitment transaction signature. This will + // only be populated if the simple taproot channels type was // negotiated. - LocalNonce *LocalMusig2Nonce - - // RemoteNoncee is an optional field that stores a remote musig2 nonce. - // This will only be populated if the simple taproot channels type was - // negotiated. - RemoteNonce *RemoteMusig2Nonce + LocalNonce *Musig2Nonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -181,9 +178,6 @@ func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error { if o.LocalNonce != nil { recordProducers = append(recordProducers, o.LocalNonce) } - if o.RemoteNonce != nil { - recordProducers = append(recordProducers, o.RemoteNonce) - } err := EncodeMessageExtraData(&o.ExtraData, recordProducers...) if err != nil { return err @@ -308,12 +302,11 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error { var ( chanType ChannelType leaseExpiry LeaseExpiry - localNonce LocalMusig2Nonce - remoteNonce RemoteMusig2Nonce + localNonce Musig2Nonce ) typeMap, err := tlvRecords.ExtractRecords( &o.UpfrontShutdownScript, &chanType, &leaseExpiry, - &localNonce, &remoteNonce, + &localNonce, ) if err != nil { return err @@ -326,12 +319,9 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { o.LeaseExpiry = &leaseExpiry } - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + if val, ok := typeMap[NonceRecordType]; ok && val == nil { o.LocalNonce = &localNonce } - if val, ok := typeMap[RemoteNonceRecordType]; ok && val == nil { - o.RemoteNonce = &remoteNonce - } o.ExtraData = tlvRecords diff --git a/lnwire/partial_sig.go b/lnwire/partial_sig.go new file mode 100644 index 00000000000..1cc52d4e2b5 --- /dev/null +++ b/lnwire/partial_sig.go @@ -0,0 +1,102 @@ +package lnwire + +import ( + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // PartialSigLength is the length of a serialized PartialSig. The sig is + // encoded as the 32 byte S value followed by the 66 nonce value. + PartialSigLen = 98 + + // PartialSigRecordType... + PartialSigRecordType = 2 +) + +// PartialSig... +type PartialSig struct { + // Nonce.... + Nonce Musig2Nonce + + // Sig... + Sig btcec.ModNScalar +} + +// NewPartialSig... +func NewPartialSig(nonce [musig2.PubNonceSize]byte, + sig btcec.ModNScalar) *PartialSig { + + return &PartialSig{ + Nonce: nonce, + Sig: sig, + } +} + +// Record... +func (p *PartialSig) Record() tlv.Record { + return tlv.MakeStaticRecord( + PartialSigRecordType, p, PartialSigLen, partialSigTypeEncoder, + partialSigTypeDecoder, + ) +} + +// partialSigTypeEncoder encodes 98-byte musig2 extended partial signature as: +// s {32} || nonce {66}. +func partialSigTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*PartialSig); ok { + sigBytes := v.Sig.Bytes() + if _, err := w.Write(sigBytes[:]); err != nil { + return err + } + if _, err := w.Write(v.Nonce[:]); err != nil { + return err + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSig") +} + +// Encode... +func (p *PartialSig) Encode(w io.Writer) error { + return partialSigTypeEncoder(w, p, nil) +} + +// partialSigTypeDecoder decodes a 98-byte musig2 extended partial signature. +func partialSigTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*PartialSig); ok && l == PartialSigLen { + var sBytes [32]byte + if _, err := io.ReadFull(r, sBytes[:]); err != nil { + return err + } + + var s btcec.ModNScalar + s.SetBytes(&sBytes) + + var nonce [66]byte + if _, err := io.ReadFull(r, nonce[:]); err != nil { + return err + } + + *v = PartialSig{ + Sig: s, + Nonce: nonce, + } + return nil + } + + return tlv.NewTypeForDecodingErr(val, "lnwire.PartialSig", l, + PartialSigLen) +} + +// Decode... +func (p *PartialSig) Decode(r io.Reader) error { + return partialSigTypeDecoder(r, p, nil, PartialSigLen) +} diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go index 6edcc324e7d..6b6b801671c 100644 --- a/lnwire/revoke_and_ack.go +++ b/lnwire/revoke_and_ack.go @@ -36,7 +36,7 @@ type RevokeAndAck struct { // LocalNonce is the next _local_ nonce for the sending party. This // allows the receiving party to propose a new commitment using their // remote nonce and the sender's local nonce. - LocalNonce *LocalMusig2Nonce + LocalNonce *Musig2Nonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -74,16 +74,14 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { return err } - var ( - musigNonce LocalMusig2Nonce - ) + var musigNonce Musig2Nonce typeMap, err := tlvRecords.ExtractRecords(&musigNonce) if err != nil { return err } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + if val, ok := typeMap[NonceRecordType]; ok && val == nil { c.LocalNonce = &musigNonce } diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index 87a2b882f76..36684a1b021 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -21,7 +21,7 @@ type Shutdown struct { // Musig2Nonce is the nonce the sender will use to sign the first co-op // sign offer. - Musig2Nonce *LocalMusig2Nonce + Musig2Nonce *Musig2Nonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -56,16 +56,14 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { return err } - var ( - musigNonce LocalMusig2Nonce - ) + var musigNonce Musig2Nonce typeMap, err := tlvRecords.ExtractRecords(&musigNonce) if err != nil { return err } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[LocalNonceRecordType]; ok && val == nil { + if val, ok := typeMap[NonceRecordType]; ok && val == nil { s.Musig2Nonce = &musigNonce } From dedd31d722cd5dd1193b4a4c0b7ca8487ee6cbf9 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 12 Jan 2023 19:23:33 -0800 Subject: [PATCH 84/87] multi: use distinct types for sig + nonce used in co-op close flow --- lnwallet/chancloser/chancloser.go | 20 +++-- lnwallet/channel.go | 4 +- lnwallet/musig2_session.go | 14 +-- lnwire/closing_signed.go | 5 +- lnwire/commit_sig.go | 6 +- lnwire/funding_created.go | 6 +- lnwire/funding_signed.go | 6 +- lnwire/lnwire_test.go | 28 ++++-- lnwire/partial_sig.go | 136 ++++++++++++++++++++++++------ lnwire/shutdown.go | 60 +++++++++++-- 10 files changed, 216 insertions(+), 69 deletions(-) diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index 3dabac2066a..82eb3039355 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -316,7 +316,7 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { if err != nil { return nil, err } - shutdown.Musig2Nonce = (*lnwire.Musig2Nonce)( + shutdown.ShutdownNonce = (*lnwire.ShutdownNonce)( &firstClosingNonce.PubNonce, ) @@ -535,7 +535,7 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // session for signing. if c.cfg.Channel.ChanType().IsTaproot() { c.musigNoncePair.SigningNonce = musig2.Nonces{ - PubNonce: *shutdownMsg.Musig2Nonce, + PubNonce: *shutdownMsg.ShutdownNonce, } } @@ -602,7 +602,7 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // session for signing. if c.cfg.Channel.ChanType().IsTaproot() { c.musigNoncePair.SigningNonce = musig2.Nonces{ - PubNonce: *shutdownMsg.Musig2Nonce, + PubNonce: *shutdownMsg.ShutdownNonce, } } @@ -715,8 +715,14 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // We'll convert the wire partial signatures into an // input.Signature compliant struct so we can pass it // into the final combination function. - localPartialSig := matchingSig.PartialSig - remotePartialSig := closeSignedMsg.PartialSig + localPartialSig := &lnwire.PartialSigWithNonce{ + PartialSig: *matchingSig.PartialSig, + Nonce: c.musigNoncePair.VerificationNonce.PubNonce, + } + remotePartialSig := &lnwire.PartialSigWithNonce{ + PartialSig: *closeSignedMsg.PartialSig, + Nonce: c.musigNoncePair.SigningNonce.PubNonce, + } localSig = new(lnwallet.MusigPartialSig).FromWireSig( localPartialSig, @@ -852,7 +858,7 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign // not. var ( parsedSig lnwire.Sig - partialSig *lnwire.PartialSig + partialSig *lnwire.PartialSigWithNonce ) if c.cfg.Channel.ChanType().IsTaproot() { musig, ok := rawSig.(*lnwallet.MusigPartialSig) @@ -881,7 +887,7 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign // over a partial signature which'll be combine donce our offer is // accepted. if partialSig != nil { - closeSignedMsg.PartialSig = partialSig + closeSignedMsg.PartialSig = &partialSig.PartialSig } // We'll also save this close signed, in the case that the remote party diff --git a/lnwallet/channel.go b/lnwallet/channel.go index b136adfc879..766e68af800 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3775,7 +3775,7 @@ type CommitSigs struct { HtlcSigs []lnwire.Sig // PartialSig... - PartialSig *lnwire.PartialSig + PartialSig *lnwire.PartialSigWithNonce } // NewCommitState wraps the various signatures needed to properly @@ -3814,7 +3814,7 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { var ( sig lnwire.Sig - partialSig *lnwire.PartialSig + partialSig *lnwire.PartialSigWithNonce htlcSigs []lnwire.Sig ) diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 82856bb625b..523a8184685 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -44,7 +44,9 @@ func NewMusigPartialSig(sig *musig2.PartialSignature, } // FromWireSig... -func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSig) *MusigPartialSig { +func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce, +) *MusigPartialSig { + p.sig = &musig2.PartialSignature{ S: &sig.Sig, } @@ -54,10 +56,10 @@ func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSig) *MusigPartialSig { } // ToWireSig... -func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSig { - return &lnwire.PartialSig{ - Nonce: p.signerNonce, - Sig: *p.sig.S, +func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSigWithNonce { + return &lnwire.PartialSigWithNonce{ + PartialSig: lnwire.NewPartialSig(*p.sig.S), + Nonce: p.signerNonce, } } @@ -341,7 +343,7 @@ func (m *MusigSession) VerificationNonce() *musig2.Nonces { // relative local nonce) returned to transmit to the remote party, which allows // them to generate another signature. func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, - sig *lnwire.PartialSig) (*musig2.Nonces, error) { + sig *lnwire.PartialSigWithNonce) (*musig2.Nonces, error) { // Before we can verify the signature, we'll need to finalize the // session by binding the remote party's provided signing nonce. diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index fb4ba1811e7..11378e8e9ef 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -31,14 +31,13 @@ type ClosingSigned struct { Signature Sig // PartialSig is used to transmit a musig2 extended partial signature - // that also carries along the public nonce of the signer. + // that signs the latest fee offer. The nonce isn't sent along side, as + // that has already been sent in the initial shutdown message. // // NOTE: This field is only populated if a musig2 taproot channel is // being signed for. In this case, the above Sig type MUST be blank. PartialSig *PartialSig - // TODO(roasbef): ^ make into diff type - // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go index 5a9af1fa236..d25d36a8add 100644 --- a/lnwire/commit_sig.go +++ b/lnwire/commit_sig.go @@ -43,7 +43,7 @@ type CommitSig struct { // // NOTE: This field is only populated if a musig2 taproot channel is // being signed for. In this case, the above Sig type MUST be blank. - PartialSig *PartialSig + PartialSig *PartialSigWithNonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -82,7 +82,7 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { } var ( - partialSig PartialSig + partialSig PartialSigWithNonce ) typeMap, err := tlvRecords.ExtractRecords(&partialSig) if err != nil { @@ -90,7 +90,7 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + if val, ok := typeMap[PartialSigWithNonceRecordType]; ok && val == nil { c.PartialSig = &partialSig } diff --git a/lnwire/funding_created.go b/lnwire/funding_created.go index b7b706a67dd..f8128ff761c 100644 --- a/lnwire/funding_created.go +++ b/lnwire/funding_created.go @@ -32,7 +32,7 @@ type FundingCreated struct { // // NOTE: This field is only populated if a musig2 taproot channel is // being signed for. In this case, the above Sig type MUST be blank. - PartialSig *PartialSig + PartialSig *PartialSigWithNonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -93,7 +93,7 @@ func (f *FundingCreated) Decode(r io.Reader, pver uint32) error { } var ( - partialSig PartialSig + partialSig PartialSigWithNonce ) typeMap, err := tlvRecords.ExtractRecords(&partialSig) if err != nil { @@ -101,7 +101,7 @@ func (f *FundingCreated) Decode(r io.Reader, pver uint32) error { } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + if val, ok := typeMap[PartialSigWithNonceRecordType]; ok && val == nil { f.PartialSig = &partialSig } diff --git a/lnwire/funding_signed.go b/lnwire/funding_signed.go index 89a6ddde9e9..c7fb03d155b 100644 --- a/lnwire/funding_signed.go +++ b/lnwire/funding_signed.go @@ -24,7 +24,7 @@ type FundingSigned struct { // // NOTE: This field is only populated if a musig2 taproot channel is // being signed for. In this case, the above Sig type MUST be blank. - PartialSig *PartialSig + PartialSig *PartialSigWithNonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -79,7 +79,7 @@ func (f *FundingSigned) Decode(r io.Reader, pver uint32) error { } var ( - partialSig PartialSig + partialSig PartialSigWithNonce ) typeMap, err := tlvRecords.ExtractRecords(&partialSig) if err != nil { @@ -87,7 +87,7 @@ func (f *FundingSigned) Decode(r io.Reader, pver uint32) error { } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + if val, ok := typeMap[PartialSigWithNonceRecordType]; ok && val == nil { f.PartialSig = &partialSig } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 565960d075c..381bc203f64 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -58,8 +58,22 @@ func randPartialSig(r *rand.Rand) (*PartialSig, error) { s.SetByteSlice(sigBytes[:]) return &PartialSig{ - Sig: s, - Nonce: *randLocalNonce(r), + Sig: s, + }, nil +} + +func randPartialSigWithNonce(r *rand.Rand) (*PartialSigWithNonce, error) { + var sigBytes [32]byte + if _, err := r.Read(sigBytes[:]); err != nil { + return nil, fmt.Errorf("unable to generate sig: %v", err) + } + + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[:]) + + return &PartialSigWithNonce{ + PartialSig: NewPartialSig(s), + Nonce: *randLocalNonce(r), }, nil } @@ -574,7 +588,7 @@ func TestLightningWireProtocol(t *testing.T) { // 1/2 chance to attach a partial sig. if r.Intn(2) == 0 { - req.PartialSig, err = randPartialSig(r) + req.PartialSig, err = randPartialSigWithNonce(r) if err != nil { t.Fatalf("unable to generate sig: %v", err) return @@ -603,7 +617,7 @@ func TestLightningWireProtocol(t *testing.T) { // 1/2 chance to attach a partial sig. if r.Intn(2) == 0 { - req.PartialSig, err = randPartialSig(r) + req.PartialSig, err = randPartialSigWithNonce(r) if err != nil { t.Fatalf("unable to generate sig: %v", err) return @@ -657,7 +671,9 @@ func TestLightningWireProtocol(t *testing.T) { } if r.Int31()%2 == 0 { - req.Musig2Nonce = randLocalNonce(r) + req.ShutdownNonce = (*ShutdownNonce)( + randLocalNonce(r), + ) } v[0] = reflect.ValueOf(req) @@ -721,7 +737,7 @@ func TestLightningWireProtocol(t *testing.T) { // 50/50 chance to attach a partial sig. if r.Int31()%2 == 0 { - req.PartialSig, err = randPartialSig(r) + req.PartialSig, err = randPartialSigWithNonce(r) if err != nil { t.Fatalf("unable to generate sig: %v", err) return diff --git a/lnwire/partial_sig.go b/lnwire/partial_sig.go index 1cc52d4e2b5..856eb636a64 100644 --- a/lnwire/partial_sig.go +++ b/lnwire/partial_sig.go @@ -9,52 +9,41 @@ import ( ) const ( - // PartialSigLength is the length of a serialized PartialSig. The sig is - // encoded as the 32 byte S value followed by the 66 nonce value. - PartialSigLen = 98 + // PartialSigLen... + PartialSigLen = 32 // PartialSigRecordType... - PartialSigRecordType = 2 + PartialSigRecordType = 6 ) // PartialSig... type PartialSig struct { - // Nonce.... - Nonce Musig2Nonce - // Sig... Sig btcec.ModNScalar } // NewPartialSig... -func NewPartialSig(nonce [musig2.PubNonceSize]byte, - sig btcec.ModNScalar) *PartialSig { - - return &PartialSig{ - Nonce: nonce, - Sig: sig, +func NewPartialSig(sig btcec.ModNScalar) PartialSig { + return PartialSig{ + Sig: sig, } } // Record... func (p *PartialSig) Record() tlv.Record { return tlv.MakeStaticRecord( - PartialSigRecordType, p, PartialSigLen, partialSigTypeEncoder, - partialSigTypeDecoder, + PartialSigRecordType, p, PartialSigLen, + partialSigTypeEncoder, partialSigTypeDecoder, ) } -// partialSigTypeEncoder encodes 98-byte musig2 extended partial signature as: -// s {32} || nonce {66}. +// partialSigTypeEncoder... func partialSigTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { if v, ok := val.(*PartialSig); ok { sigBytes := v.Sig.Bytes() if _, err := w.Write(sigBytes[:]); err != nil { return err } - if _, err := w.Write(v.Nonce[:]); err != nil { - return err - } return nil } @@ -67,7 +56,8 @@ func (p *PartialSig) Encode(w io.Writer) error { return partialSigTypeEncoder(w, p, nil) } -// partialSigTypeDecoder decodes a 98-byte musig2 extended partial signature. +// partialSigWithNonceTypeDecoder decodes a 98-byte musig2 extended partial +// signature. func partialSigTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { @@ -80,14 +70,8 @@ func partialSigTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, var s btcec.ModNScalar s.SetBytes(&sBytes) - var nonce [66]byte - if _, err := io.ReadFull(r, nonce[:]); err != nil { - return err - } - *v = PartialSig{ - Sig: s, - Nonce: nonce, + Sig: s, } return nil } @@ -100,3 +84,99 @@ func partialSigTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, func (p *PartialSig) Decode(r io.Reader) error { return partialSigTypeDecoder(r, p, nil, PartialSigLen) } + +const ( + // PartialSigWithNonceLength is the length of a serialized + // PartialSigWithNonce. The sig is encoded as the 32 byte S value + // followed by the 66 nonce value. + PartialSigWithNonceLen = 98 + + // PartialSigWithNonceRecordType... + PartialSigWithNonceRecordType = 2 +) + +// PartialSigWithNonce... +type PartialSigWithNonce struct { + PartialSig + + // Nonce.... + Nonce Musig2Nonce +} + +// NewPartialSigWithNonce... +func NewPartialSigWithNonce(nonce [musig2.PubNonceSize]byte, + sig btcec.ModNScalar) *PartialSigWithNonce { + + return &PartialSigWithNonce{ + Nonce: nonce, + PartialSig: NewPartialSig(sig), + } +} + +// Record... +func (p *PartialSigWithNonce) Record() tlv.Record { + return tlv.MakeStaticRecord( + PartialSigWithNonceRecordType, p, PartialSigWithNonceLen, + partialSigWithNonceTypeEncoder, partialSigWithNonceTypeDecoder, + ) +} + +// partialSigWithNonceTypeEncoder encodes 98-byte musig2 extended partial +// signature as: s {32} || nonce {66}. +func partialSigWithNonceTypeEncoder(w io.Writer, val interface{}, + buf *[8]byte) error { + + if v, ok := val.(*PartialSigWithNonce); ok { + sigBytes := v.Sig.Bytes() + if _, err := w.Write(sigBytes[:]); err != nil { + return err + } + if _, err := w.Write(v.Nonce[:]); err != nil { + return err + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSigWithNonce") +} + +// Encode... +func (p *PartialSigWithNonce) Encode(w io.Writer) error { + return partialSigTypeEncoder(w, p, nil) +} + +// partialSigWithNonceTypeDecoder decodes a 98-byte musig2 extended partial +// signature. +func partialSigWithNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*PartialSigWithNonce); ok && l == PartialSigWithNonceLen { + var sBytes [32]byte + if _, err := io.ReadFull(r, sBytes[:]); err != nil { + return err + } + + var s btcec.ModNScalar + s.SetBytes(&sBytes) + + var nonce [66]byte + if _, err := io.ReadFull(r, nonce[:]); err != nil { + return err + } + + *v = PartialSigWithNonce{ + PartialSig: NewPartialSig(s), + Nonce: nonce, + } + return nil + } + + return tlv.NewTypeForDecodingErr(val, "lnwire.PartialSigWithNonce", l, + PartialSigWithNonceLen) +} + +// Decode... +func (p *PartialSigWithNonce) Decode(r io.Reader) error { + return partialSigTypeDecoder(r, p, nil, PartialSigWithNonceLen) +} diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index 36684a1b021..43f4f361b45 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -4,9 +4,53 @@ import ( "bytes" "io" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/lightningnetwork/lnd/tlv" ) +const ( + // ShutdownNonceRecordType... + ShutdownNonceRecordType = 8 +) + +// ShutdownNonce... +type ShutdownNonce Musig2Nonce + +// Record returns a TLV record that can be used to encode/decode the musig2 +// nonce from a given TLV stream. +func (s *ShutdownNonce) Record() tlv.Record { + return tlv.MakeStaticRecord( + ShutdownNonceRecordType, s, musig2.PubNonceSize, + shutdownNonceTypeEncoder, shutdownNonceTypeDecoder, + ) +} + +// shutdownNonceTypeEncoder is a custom TLV encoder for the Musig2Nonce type. +func shutdownNonceTypeEncoder(w io.Writer, val interface{}, + buf *[8]byte) error { + + if v, ok := val.(*ShutdownNonce); ok { + _, err := w.Write(v[:]) + return err + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") +} + +// shutdownNonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. +func shutdownNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*ShutdownNonce); ok { + _, err := io.ReadFull(r, v[:]) + return err + } + + return tlv.NewTypeForDecodingErr( + val, "lnwire.ShutdownNonce", l, musig2.PubNonceSize, + ) +} + // Shutdown is sent by either side in order to initiate the cooperative closure // of a channel. This message is sparse as both sides implicitly have the // information necessary to construct a transaction that will send the settled @@ -19,9 +63,9 @@ type Shutdown struct { // Address is the script to which the channel funds will be paid. Address DeliveryAddress - // Musig2Nonce is the nonce the sender will use to sign the first co-op - // sign offer. - Musig2Nonce *Musig2Nonce + // ShutdownNonce is the nonce the sender will use to sign the first + // co-op sign offer. + ShutdownNonce *ShutdownNonce // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can @@ -56,15 +100,15 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { return err } - var musigNonce Musig2Nonce + var musigNonce ShutdownNonce typeMap, err := tlvRecords.ExtractRecords(&musigNonce) if err != nil { return err } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[NonceRecordType]; ok && val == nil { - s.Musig2Nonce = &musigNonce + if val, ok := typeMap[ShutdownNonceRecordType]; ok && val == nil { + s.ShutdownNonce = &musigNonce } if len(tlvRecords) != 0 { @@ -80,8 +124,8 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { // This is part of the lnwire.Message interface. func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error { recordProducers := make([]tlv.RecordProducer, 0, 1) - if s.Musig2Nonce != nil { - recordProducers = append(recordProducers, s.Musig2Nonce) + if s.ShutdownNonce != nil { + recordProducers = append(recordProducers, s.ShutdownNonce) } err := EncodeMessageExtraData(&s.ExtraData, recordProducers...) if err != nil { From e68221cb39e0dbfd2b8a608d008297f267082712 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 12 Jan 2023 19:55:14 -0800 Subject: [PATCH 85/87] multi: use counter based (shachain) nonces for all local nonces This allows us to not have to store any persistent nonce state to disk. When we go to broadcast, we'll re-generate our nonce based on the current shachain height, then use that to generate the combined nonce and sign the next state. --- funding/manager.go | 2 ++ htlcswitch/link.go | 2 ++ lnwallet/channel.go | 27 ++++++++++++++++++++++----- lnwallet/wallet.go | 9 ++++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index b95a339abd4..079cc03950c 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -3597,6 +3597,8 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, // then we'll generate one now. verNonce, err := lnwallet.NewMusigVerificationNonce( channel.LocalChanCfg.MultiSigKey.PubKey, + channel.LocalCommitment.CommitHeight, + channel.RevocationProducer, ) if err != nil { f.nonceMtx.Unlock() diff --git a/htlcswitch/link.go b/htlcswitch/link.go index e5fbecec569..c6b9c8e3a8a 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -696,6 +696,8 @@ func (l *channelLink) syncChanStates() error { // If this is a tarpoot channel, then in addition to the normal reest // message, we'll also send our local+remote nonces as well. + // + // TODO(roasbeef): move into ChanSyncMsg if chanState.ChanType.IsTaproot() { localNonce, err := l.channel.GenMusigNonces() if err != nil { diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 766e68af800..f148273f03d 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/shachain" ) var ( @@ -7550,7 +7551,8 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd revocationMsg.NextRevocationKey = input.ComputeCommitmentPoint(nextCommitSecret[:]) revocationMsg.ChanID = lnwire.NewChanIDFromOutPoint( - &lc.channelState.FundingOutpoint) + &lc.channelState.FundingOutpoint, + ) return revocationMsg, nil } @@ -7902,6 +7904,7 @@ func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { var err error lc.pendingVerificationNonce, err = NewMusigVerificationNonce( lc.channelState.LocalChanCfg.MultiSigKey.PubKey, + lc.currentHeight, lc.channelState.RevocationProducer, ) if err != nil { return nil, err @@ -7910,13 +7913,27 @@ func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { return lc.pendingVerificationNonce, nil } -// NewMusigVerificationNonce... -func NewMusigVerificationNonce(pubKey *btcec.PublicKey, -) (*musig2.Nonces, error) { +// NewMusigVerificationNonce generates the local or verification nonce for +// another musig2 session. In order to permit our implementation to not have to +// write any secret nonce state to disk, we'll use the _next_ shachain +// pre-image as our primary randomness source. +func NewMusigVerificationNonce(pubKey *btcec.PublicKey, currentHeight uint64, + shaGen shachain.Producer) (*musig2.Nonces, error) { + + // At this point, we're on currentHeight and need to generate a nonce + // to be used for the next commitment from the remote party, so we use + // a +1 here. + // + // TODO(roasbeef): unit tests + nextPreimage, err := shaGen.AtIndex(currentHeight + 1) + if err != nil { + return nil, err + } + shaChainRand := musig2.WithCustomRand(bytes.NewBuffer(nextPreimage[:])) pubKeyOpt := musig2.WithPublicKey(pubKey) - return musig2.GenNonces(pubKeyOpt) + return musig2.GenNonces(pubKeyOpt, shaChainRand) } // HasRemoteNonces returns true if the channel has a remote nonce pair. diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 59fd06a3203..c0556c18861 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -1212,11 +1212,18 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation, // nonces: one for our local commitment, and one for their remote // commitment. if reservation.partialState.ChanType.IsTaproot() { + // As we'd like the local nonce we send over to be generated + // determinstically, we'll provide a custom reader that + // actually just uses our sha-chain pre-image as the primary + // randomness source. + shaChainRand := musig2.WithCustomRand( + bytes.NewBuffer(firstPreimage[:]), + ) pubKeyOpt := musig2.WithPublicKey( reservation.ourContribution.MultiSigKey.PubKey, ) reservation.ourContribution.LocalNonce, err = musig2.GenNonces( - pubKeyOpt, + pubKeyOpt, shaChainRand, ) if err != nil { return err From e69ffe8251247e18b467df8feb925bac2e1374ca Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:11:33 -0800 Subject: [PATCH 86/87] lnwire: fix bug with PartialSigWithNonce.Decode/Encode --- lnwire/partial_sig.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lnwire/partial_sig.go b/lnwire/partial_sig.go index 856eb636a64..b4c9ccbec13 100644 --- a/lnwire/partial_sig.go +++ b/lnwire/partial_sig.go @@ -143,7 +143,7 @@ func partialSigWithNonceTypeEncoder(w io.Writer, val interface{}, // Encode... func (p *PartialSigWithNonce) Encode(w io.Writer) error { - return partialSigTypeEncoder(w, p, nil) + return partialSigWithNonceTypeEncoder(w, p, nil) } // partialSigWithNonceTypeDecoder decodes a 98-byte musig2 extended partial @@ -178,5 +178,7 @@ func partialSigWithNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, // Decode... func (p *PartialSigWithNonce) Decode(r io.Reader) error { - return partialSigTypeDecoder(r, p, nil, PartialSigWithNonceLen) + return partialSigWithNonceTypeDecoder( + r, p, nil, PartialSigWithNonceLen, + ) } From ded917be9fcf6164fe362ad3384639a7a6ba36db Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:15:40 -0800 Subject: [PATCH 87/87] multi: add initial support for force closing Force closing works, but the resolvers stop short as the contract court hasn't been updated yet for any of the new flows. --- funding/manager.go | 3 +- lnwallet/channel.go | 169 ++++++++++++++++++++++++------ lnwallet/chanvalidate/validate.go | 10 +- lnwallet/musig2_session.go | 55 +++++++--- lnwallet/wallet.go | 29 ++--- 5 files changed, 195 insertions(+), 71 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 079cc03950c..8e3f5140ffc 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -2807,7 +2807,6 @@ func (f *Manager) handleFundingConfirmation( // Now that that the channel has been fully confirmed, we'll request // that the wallet fully verify this channel to ensure that it can be // used. - log.Infof("taproot: %v", completeChan.ChanType.IsTaproot()) err := f.cfg.Wallet.ValidateChannel(completeChan, confChannel.fundingTx) if err != nil { // TODO(roasbeef): delete chan state? @@ -3598,7 +3597,7 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, verNonce, err := lnwallet.NewMusigVerificationNonce( channel.LocalChanCfg.MultiSigKey.PubKey, channel.LocalCommitment.CommitHeight, - channel.RevocationProducer, + channel.RevocationProducer, false, ) if err != nil { f.nonceMtx.Unlock() diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f148273f03d..369bd1046e8 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -4809,8 +4809,16 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { if lc.channelState.ChanType.IsTaproot() { localSession := lc.musigSessions.LocalSession + // As we want to ensure we never write nonces to disk, we'll + // use the shachain state to generate a nonce for our next + // local state. Similar to generateRevocation, we do height + 2 + // (next height + 1) here, as this is for the _next_ local + // state, and we're about to accept height + 1. + localCtrNonce := WithLocalCounterNonce( + nextHeight+1, lc.channelState.RevocationProducer, + ) nextVerificationNonce, err := localSession.VerifyCommitSig( - localCommitTx, commitSigs.PartialSig, + localCommitTx, commitSigs.PartialSig, localCtrNonce, ) if err != nil { // TODO(roasbeef): new InvalidPartialCommitSigError @@ -4897,8 +4905,21 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { } // The signature checks out, so we can now add the new commitment to - // our local commitment chain. - localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() + // our local commitment chain. For regular channels, we can just + // serialize the ECDSA sig. For taproot channels, we'll serialize the + // partial sig that includes the nonce that was used for signing. + if lc.channelState.ChanType.IsTaproot() { + var sigBytes [lnwire.PartialSigWithNonceLen]byte + b := bytes.NewBuffer(sigBytes[0:0]) + if err := commitSigs.PartialSig.Encode(b); err != nil { + return err + } + + localCommitmentView.sig = sigBytes[:] + } else { + localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() + } + lc.localCommitChain.addCommitment(localCommitmentView) return nil @@ -5953,33 +5974,106 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { localCommit := lc.channelState.LocalCommitment commitTx := localCommit.CommitTx.Copy() - // TODO(roasbeef): update for taproot - // * also want the full R sig as well + ourKey := lc.channelState.LocalChanCfg.MultiSigKey + theirKey := lc.channelState.RemoteChanCfg.MultiSigKey - theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) - if err != nil { - return nil, err - } + var witness wire.TxWitness + switch { + // If this is a taproot channel, then we'll need to re-derive the nonce + // we need to generate a new signature + case lc.channelState.ChanType.IsTaproot(): + // First, we'll need to re-derive the local nonce we sent to + // the remote party to create this musig session. For the + // target height we pass in 1 minus the current height, as + // NewMusigVerificationNonce is used to create the nonce for + // the _next_ height. + // + // TODO(roasbeef): make into func for unit tests + localNonce, err := NewMusigVerificationNonce( + ourKey.PubKey, lc.currentHeight, + lc.channelState.RevocationProducer, true, + ) + if err != nil { + return nil, err + } - // With this, we then generate the full witness so the caller can - // broadcast a fully signed transaction. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) - ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) - if err != nil { - return nil, err - } + // Now that we have the local nonce, we'll re-create the musig + // session we had for this height. + musigSession := NewPartialMusigSession( + *localNonce, ourKey, theirKey, lc.Signer, + &lc.fundingOutput, false, + ) - // With the final signature generated, create the witness stack - // required to spend from the multi-sig output. - ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() + var remoteSig lnwire.PartialSigWithNonce + err = remoteSig.Decode( + bytes.NewReader(localCommit.CommitSig), + ) + if err != nil { + return nil, fmt.Errorf("unable to decode remote "+ + "partial sig: %w", err) + } - commitTx.TxIn[0].Witness = input.SpendMultiSig( - lc.signDesc.WitnessScript, ourKey, - ourSig, theirKey, theirSig, - ) + // Next, we'll manually finalize the session with the signing + // nonce we got from the remote party which is embedded in the + // signature we have. + err = musigSession.FinalizeSession(musig2.Nonces{ + PubNonce: remoteSig.Nonce, + }) + if err != nil { + return nil, fmt.Errorf("unable to finalize musig "+ + "session: %w", err) + } + + // Now that the session has been finalized, we can generate our + // half of the signature for the state. We don't capture the + // sig as it's stored within the session. + if _, err := musigSession.SignCommit(commitTx); err != nil { + return nil, err + } + + // The final step is now to combine this signature we generated + // above, with the remote party's signature. We only need to + // pass the remote sig, as the local sig was already cached in + // the session. + var partialSig MusigPartialSig + partialSig.FromWireSig(&remoteSig) + finalSig, err := musigSession.CombineSigs(partialSig.sig) + if err != nil { + return nil, fmt.Errorf("unable to combine musig "+ + "partial sigs: %w", err) + } + + // The witness is the single keyspend schnorr sig. + witness = wire.TxWitness{ + finalSig.Serialize(), + } + + // Otherwise, the final witness we generate will be a normal p2wsh + // multi-sig spend. + default: + theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) + if err != nil { + return nil, err + } + + // With this, we then generate the full witness so the caller + // can broadcast a fully signed transaction. + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) + ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) + if err != nil { + return nil, err + } + + // With the final signature generated, create the witness stack + // required to spend from the multi-sig output. + witness = input.SpendMultiSig( + lc.signDesc.WitnessScript, + ourKey.PubKey.SerializeCompressed(), ourSig, + theirKey.PubKey.SerializeCompressed(), theirSig, + ) + } + + commitTx.TxIn[0].Witness = witness return commitTx, nil } @@ -7905,6 +7999,7 @@ func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { lc.pendingVerificationNonce, err = NewMusigVerificationNonce( lc.channelState.LocalChanCfg.MultiSigKey.PubKey, lc.currentHeight, lc.channelState.RevocationProducer, + false, ) if err != nil { return nil, err @@ -7918,14 +8013,22 @@ func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { // write any secret nonce state to disk, we'll use the _next_ shachain // pre-image as our primary randomness source. func NewMusigVerificationNonce(pubKey *btcec.PublicKey, currentHeight uint64, - shaGen shachain.Producer) (*musig2.Nonces, error) { + shaGen shachain.Producer, forBroadcast bool) (*musig2.Nonces, error) { - // At this point, we're on currentHeight and need to generate a nonce - // to be used for the next commitment from the remote party, so we use - // a +1 here. - // - // TODO(roasbeef): unit tests - nextPreimage, err := shaGen.AtIndex(currentHeight + 1) + // If we're broadcasting this commitment, then we need to get the nonce + // for the current height. Otherwise, we'll add one, as we're + // generating a local nonce for the _next_ height. + targetHeight := func() uint64 { + if forBroadcast { + return currentHeight + } + + return currentHeight + 1 + }() + + // Now that we know what height we need, we'll grab the shachain + // pre-image at the target destination. + nextPreimage, err := shaGen.AtIndex(targetHeight) if err != nil { return nil, err } diff --git a/lnwallet/chanvalidate/validate.go b/lnwallet/chanvalidate/validate.go index c349ea974b5..5cf5bbf1001 100644 --- a/lnwallet/chanvalidate/validate.go +++ b/lnwallet/chanvalidate/validate.go @@ -186,10 +186,14 @@ func Validate(ctx *Context) (*wire.OutPoint, error) { // If we reach this point, then all other checks have succeeded, so // we'll now attempt a full Script VM execution to ensure that we're // able to close the channel using this initial state. + prevFetcher := txscript.NewCannedPrevOutputFetcher( + ctx.MultiSigPkScript, fundingValue, + ) + commitTx := ctx.CommitCtx.FullySignedCommitTx + hashCache := txscript.NewTxSigHashes(commitTx, prevFetcher) vm, err := txscript.NewEngine( - ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx, - 0, txscript.StandardVerifyFlags, nil, nil, fundingValue, - txscript.NewCannedPrevOutputFetcher(ctx.MultiSigPkScript, 0), + ctx.MultiSigPkScript, commitTx, 0, txscript.StandardVerifyFlags, + nil, hashCache, fundingValue, prevFetcher, ) if err != nil { return nil, err diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go index 523a8184685..74757b7fb42 100644 --- a/lnwallet/musig2_session.go +++ b/lnwallet/musig2_session.go @@ -3,6 +3,7 @@ package lnwallet import ( "bytes" "fmt" + "io" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -13,6 +14,7 @@ import ( "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/shachain" ) // MusigPartialSig... @@ -316,10 +318,6 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) { func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces, ) (*MusigSession, error) { - // TODO(roasbeef): need to also pass along verification nonce - // * local commit: called after recv'ing a sig - // * remote commit: called when get revoke-and-ack - return NewPartialMusigSession( *verificationNonce, m.localKey, m.remoteKey, m.signer, m.inputTxOut, m.remoteCommit, @@ -331,11 +329,29 @@ func (m *MusigSession) VerificationNonce() *musig2.Nonces { return &m.nonces.VerificationNonce } -// TODO(roasbeef): re hot signatures, maybe would re-use the state less signing -// thing after all? -// -// * then able to safely generate nonce deterministically when it comes to -// signing? +// musigSessionOpts... +type musigSessionOpts struct { + customRand io.Reader +} + +// defaultMusigSessionOpts... +func defaultMusigSessionOpts() *musigSessionOpts { + return &musigSessionOpts{} +} + +// MusigSessionOpt... +type MusigSessionOpt func(*musigSessionOpts) + +// WithLocalCounterNonce... +func WithLocalCounterNonce(targetHeight uint64, + shaGen shachain.Producer) MusigSessionOpt { + + return func(opt *musigSessionOpts) { + nextPreimage, _ := shaGen.AtIndex(targetHeight) + + opt.customRand = bytes.NewBuffer(nextPreimage[:]) + } +} // VerifyCommitSig attempts to verify the passed partial signature against the // passed commitment transaction. A keyspend sighash is assumed to generate the @@ -343,7 +359,13 @@ func (m *MusigSession) VerificationNonce() *musig2.Nonces { // relative local nonce) returned to transmit to the remote party, which allows // them to generate another signature. func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, - sig *lnwire.PartialSigWithNonce) (*musig2.Nonces, error) { + sig *lnwire.PartialSigWithNonce, + musigOpts ...MusigSessionOpt) (*musig2.Nonces, error) { + + opts := defaultMusigSessionOpts() + for _, optFunc := range musigOpts { + optFunc(opts) + } // Before we can verify the signature, we'll need to finalize the // session by binding the remote party's provided signing nonce. @@ -379,12 +401,19 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, return nil, fmt.Errorf("invalid partial commit sig") } + nonceOpts := []musig2.NonceGenOption{ + musig2.WithPublicKey(m.localKey.PubKey), + } + if opts.customRand != nil { + nonceOpts = append( + nonceOpts, musig2.WithCustomRand(opts.customRand), + ) + } + // At this point, we know that their signature is valid, so we'll // generate another verification nonce for them, so they can generate a // new state transition. - nextVerificationNonce, err := musig2.GenNonces( - musig2.WithPublicKey(m.localKey.PubKey), - ) + nextVerificationNonce, err := musig2.GenNonces(nonceOpts...) if err != nil { return nil, fmt.Errorf("unable to gen new nonce: %w", err) } diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index c0556c18861..eb446bc29dc 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -2277,27 +2277,18 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, // First, we'll obtain a fully signed commitment transaction so we can // pass into it on the chanvalidate package for verification. - // - // TODO(roasbeef): need to pass in nonce state? or already should have - // own sig on disk channel, err := NewLightningChannel(l.Cfg.Signer, channelState, nil) if err != nil { return err } - // TODO(roasbeef): need to update getSignedCommitTx for taproot, need - // to go w/ the deterministic nonces so can sign own version - localKey := channelState.LocalChanCfg.MultiSigKey.PubKey remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey // We'll also need the multi-sig witness script itself so the // chanvalidate package can check it for correctness against the // funding transaction, and also commitment validity. - var ( - fundingScript []byte - commitCtx *chanvalidate.CommitmentContext - ) + var fundingScript []byte if channelState.ChanType.IsTaproot() { walletLog.Debugf("validating taproot channel: ChannelPoint(%v)", channelState.FundingOutpoint) @@ -2309,8 +2300,6 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, return err } - // TODO(roasbeef): remove temp - commitCtx = nil } else { walletLog.Debugf("validating p2wsh channel: ChannelPoint(%v)", channelState.FundingOutpoint) @@ -2326,15 +2315,15 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, if err != nil { return err } + } - signedCommitTx, err := channel.getSignedCommitTx() - if err != nil { - return err - } - commitCtx = &chanvalidate.CommitmentContext{ - Value: channel.Capacity, - FullySignedCommitTx: signedCommitTx, - } + signedCommitTx, err := channel.getSignedCommitTx() + if err != nil { + return err + } + commitCtx := &chanvalidate.CommitmentContext{ + Value: channel.Capacity, + FullySignedCommitTx: signedCommitTx, } // Finally, we'll pass in all the necessary context needed to fully