Skip to content

Commit 60613f8

Browse files
authored
Merge pull request #885 from lightninglabs/custom-channels-integration-signing
[custom channels 2/3]: add aux leaf signer
2 parents 1f580d7 + feea448 commit 60613f8

File tree

12 files changed

+1579
-15
lines changed

12 files changed

+1579
-15
lines changed

config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ type Config struct {
128128

129129
AuxLeafCreator *tapchannel.AuxLeafCreator
130130

131+
AuxLeafSigner *tapchannel.AuxLeafSigner
132+
131133
// UniversePublicAccess is flag which, If true, and the Universe server
132134
// is on a public interface, valid proof from remote parties will be
133135
// accepted, and proofs will be queryable by remote parties.

server.go

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sync/atomic"
1010
"time"
1111

12+
"github.com/btcsuite/btcd/wire"
1213
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
1314
"github.com/lightninglabs/lndclient"
1415
"github.com/lightninglabs/taproot-assets/fn"
@@ -181,10 +182,13 @@ func (s *Server) initialize(interceptorChain *rpcperms.InterceptorChain) error {
181182
return fmt.Errorf("unable to start RFQ manager: %w", err)
182183
}
183184

184-
// Start the auxiliary leaf creator.
185+
// Start the auxiliary leaf creator and signer.
185186
if err := s.cfg.AuxLeafCreator.Start(); err != nil {
186187
return fmt.Errorf("unable to start aux leaf creator: %w", err)
187188
}
189+
if err := s.cfg.AuxLeafSigner.Start(); err != nil {
190+
return fmt.Errorf("unable to start aux leaf signer: %w", err)
191+
}
188192

189193
if s.cfg.UniversePublicAccess {
190194
err := s.cfg.UniverseFederation.SetAllowPublicAccess()
@@ -625,6 +629,9 @@ func (s *Server) Stop() error {
625629
if err := s.cfg.AuxLeafCreator.Stop(); err != nil {
626630
return err
627631
}
632+
if err := s.cfg.AuxLeafSigner.Stop(); err != nil {
633+
return err
634+
}
628635

629636
if s.macaroonService != nil {
630637
err := s.macaroonService.Stop()
@@ -641,9 +648,10 @@ func (s *Server) Stop() error {
641648
}
642649

643650
// A compile-time check to ensure that Server fully implements the
644-
// lnwallet.AuxLeafStore and lnd.AuxDataParser interfaces.
651+
// lnwallet.AuxLeafStore, lnd.AuxDataParser and lnwallet.AuxSigner interfaces.
645652
var _ lnwallet.AuxLeafStore = (*Server)(nil)
646653
var _ lnd.AuxDataParser = (*Server)(nil)
654+
var _ lnwallet.AuxSigner = (*Server)(nil)
647655

648656
// FetchLeavesFromView attempts to fetch the auxiliary leaves that correspond to
649657
// the passed aux blob, and pending fully evaluated HTLC view.
@@ -759,3 +767,80 @@ func (s *Server) InlineParseCustomData(msg proto.Message) error {
759767
// following function is fully stateless.
760768
return cmsg.ParseCustomChannelData(msg)
761769
}
770+
771+
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and processes them
772+
// asynchronously.
773+
//
774+
// NOTE: This method is part of the lnwallet.AuxSigner interface.
775+
func (s *Server) SubmitSecondLevelSigBatch(chanState *channeldb.OpenChannel,
776+
commitTx *wire.MsgTx, sigJob []lnwallet.AuxSigJob) error {
777+
778+
srvrLog.Debugf("SubmitSecondLevelSigBatch called, numSigs=%d",
779+
len(sigJob))
780+
781+
select {
782+
case <-s.ready:
783+
case <-s.quit:
784+
return fmt.Errorf("tapd is shutting down")
785+
}
786+
787+
return s.cfg.AuxLeafSigner.SubmitSecondLevelSigBatch(
788+
chanState, commitTx, sigJob,
789+
)
790+
}
791+
792+
// PackSigs takes a series of aux signatures and packs them into a single blob
793+
// that can be sent alongside the CommitSig messages.
794+
//
795+
// NOTE: This method is part of the lnwallet.AuxSigner interface.
796+
func (s *Server) PackSigs(
797+
blob []lfn.Option[tlv.Blob]) (lfn.Option[tlv.Blob], error) {
798+
799+
srvrLog.Debugf("PackSigs called")
800+
801+
select {
802+
case <-s.ready:
803+
case <-s.quit:
804+
return lfn.None[tlv.Blob](), fmt.Errorf("tapd is shutting down")
805+
}
806+
807+
return s.cfg.AuxLeafSigner.PackSigs(blob)
808+
}
809+
810+
// UnpackSigs takes a packed blob of signatures and returns the original
811+
// signatures for each HTLC, keyed by HTLC index.
812+
//
813+
// NOTE: This method is part of the lnwallet.AuxSigner interface.
814+
func (s *Server) UnpackSigs(blob lfn.Option[tlv.Blob]) ([]lfn.Option[tlv.Blob],
815+
error) {
816+
817+
srvrLog.Debugf("UnpackSigs called")
818+
819+
select {
820+
case <-s.ready:
821+
case <-s.quit:
822+
return nil, fmt.Errorf("tapd is shutting down")
823+
}
824+
825+
return s.cfg.AuxLeafSigner.UnpackSigs(blob)
826+
}
827+
828+
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux sig
829+
// jobs.
830+
//
831+
// NOTE: This method is part of the lnwallet.AuxSigner interface.
832+
func (s *Server) VerifySecondLevelSigs(chanState *channeldb.OpenChannel,
833+
commitTx *wire.MsgTx, verifyJob []lnwallet.AuxVerifyJob) error {
834+
835+
srvrLog.Debugf("VerifySecondLevelSigs called")
836+
837+
select {
838+
case <-s.ready:
839+
case <-s.quit:
840+
return fmt.Errorf("tapd is shutting down")
841+
}
842+
843+
return s.cfg.AuxLeafSigner.VerifySecondLevelSigs(
844+
chanState, commitTx, verifyJob,
845+
)
846+
}

tapcfg/server.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,12 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
348348
ChainParams: &tapChainParams,
349349
},
350350
)
351+
auxLeafSigner := tapchannel.NewAuxLeafSigner(
352+
&tapchannel.LeafSignerConfig{
353+
ChainParams: &tapChainParams,
354+
Signer: assetWallet,
355+
},
356+
)
351357

352358
return &tap.Config{
353359
DebugLevel: cfg.DebugLevel,
@@ -423,6 +429,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
423429
UniverseQueriesBurst: cfg.Universe.UniverseQueriesBurst,
424430
RfqManager: rfqManager,
425431
AuxLeafCreator: auxLeafCreator,
432+
AuxLeafSigner: auxLeafSigner,
426433
LogWriter: cfg.LogWriter,
427434
DatabaseConfig: &tap.DatabaseConfig{
428435
RootKeyStore: tapdb.NewRootKeyStore(rksDB),

tapchannel/auf_leaf_signer_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package tapchannel
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
"time"
11+
12+
"github.com/btcsuite/btcd/wire"
13+
"github.com/lightninglabs/taproot-assets/address"
14+
"github.com/lightninglabs/taproot-assets/asset"
15+
"github.com/lightninglabs/taproot-assets/internal/test"
16+
"github.com/lightninglabs/taproot-assets/proof"
17+
cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg"
18+
"github.com/lightninglabs/taproot-assets/tapfreighter"
19+
"github.com/lightninglabs/taproot-assets/tappsbt"
20+
"github.com/lightningnetwork/lnd/channeldb"
21+
lfn "github.com/lightningnetwork/lnd/fn"
22+
"github.com/lightningnetwork/lnd/input"
23+
"github.com/lightningnetwork/lnd/lnwallet"
24+
"github.com/lightningnetwork/lnd/lnwire"
25+
"github.com/lightningnetwork/lnd/tlv"
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
const (
30+
// testDataFileName is the name of the directory with the test data.
31+
testDataFileName = "testdata"
32+
)
33+
34+
var (
35+
testChainParams = &address.RegressionNetTap
36+
37+
// Block 100002 with 9 transactions on bitcoin mainnet.
38+
oddTxBlockHexFileName = filepath.Join(
39+
testDataFileName, "odd-block.hex",
40+
)
41+
42+
testTimeout = time.Second
43+
)
44+
45+
// TestAuxLeafSigner tests the AuxLeafSigner implementation.
46+
func TestAuxLeafSigner(t *testing.T) {
47+
cfg := &LeafSignerConfig{
48+
ChainParams: testChainParams,
49+
Signer: &mockVirtualSigner{},
50+
}
51+
52+
signer := NewAuxLeafSigner(cfg)
53+
require.NoError(t, signer.Start())
54+
55+
defer func() {
56+
require.NoError(t, signer.Stop())
57+
}()
58+
59+
chanState := &channeldb.OpenChannel{
60+
ChanType: channeldb.AnchorOutputsBit |
61+
channeldb.ScidAliasChanBit | channeldb.SingleFunderBit |
62+
channeldb.SimpleTaprootFeatureBit |
63+
channeldb.TapscriptRootBit,
64+
IsInitiator: true,
65+
}
66+
randInputProof := randProof(t)
67+
commitTx := &randInputProof.AnchorTx
68+
keyRing := lnwallet.CommitmentKeyRing{
69+
CommitPoint: test.RandPubKey(t),
70+
LocalCommitKeyTweak: test.RandBytes(32),
71+
LocalHtlcKeyTweak: test.RandBytes(32),
72+
LocalHtlcKey: test.RandPubKey(t),
73+
RemoteHtlcKey: test.RandPubKey(t),
74+
ToLocalKey: test.RandPubKey(t),
75+
ToRemoteKey: test.RandPubKey(t),
76+
RevocationKey: test.RandPubKey(t),
77+
}
78+
79+
outgoingHtlcs := make(map[input.HtlcIndex][]*cmsg.AssetOutput)
80+
outgoingHtlcs[0] = []*cmsg.AssetOutput{
81+
cmsg.NewAssetOutput(
82+
randInputProof.Asset.ID(), randInputProof.Asset.Amount,
83+
randInputProof,
84+
),
85+
}
86+
87+
com := cmsg.NewCommitment(
88+
nil, nil, outgoingHtlcs, nil, lnwallet.CommitAuxLeaves{},
89+
)
90+
91+
randKeyDesc, _ := test.RandKeyDesc(t)
92+
93+
jobs := []lnwallet.AuxSigJob{
94+
{
95+
SignDesc: input.SignDescriptor{
96+
KeyDesc: randKeyDesc,
97+
},
98+
BaseAuxJob: lnwallet.BaseAuxJob{
99+
OutputIndex: 0,
100+
KeyRing: keyRing,
101+
HTLC: lnwallet.PaymentDescriptor{
102+
HtlcIndex: 0,
103+
Amount: lnwire.NewMSatFromSatoshis(
104+
354,
105+
),
106+
EntryType: lnwallet.Add,
107+
},
108+
Incoming: false,
109+
CommitBlob: lfn.Some[tlv.Blob](com.Bytes()),
110+
HtlcLeaf: input.AuxTapLeaf{},
111+
},
112+
Resp: make(chan lnwallet.AuxSigJobResp),
113+
Cancel: make(chan struct{}),
114+
},
115+
}
116+
117+
err := signer.SubmitSecondLevelSigBatch(chanState, commitTx, jobs)
118+
require.NoError(t, err)
119+
120+
select {
121+
case resp := <-jobs[0].Resp:
122+
require.NoError(t, resp.Err)
123+
require.True(t, resp.SigBlob.IsSome())
124+
require.True(t, bytes.Contains(
125+
resp.SigBlob.UnwrapOr(nil),
126+
[]byte("this is a signature"),
127+
))
128+
129+
case <-time.After(testTimeout):
130+
t.Fatalf("timeout waiting for response")
131+
}
132+
}
133+
134+
// mockVirtualSigner is a mock implementation of the VirtualSigner interface.
135+
type mockVirtualSigner struct {
136+
}
137+
138+
// SignVirtualPacket signs the virtual transaction of the given packet and
139+
// returns the input indexes that were signed.
140+
//
141+
// NOTE: This is part of the VirtualPacketSigner interface.
142+
func (m *mockVirtualSigner) SignVirtualPacket(vPkt *tappsbt.VPacket,
143+
_ ...tapfreighter.SignVirtualPacketOption) ([]uint32,
144+
error) {
145+
146+
// A second-level HTLC transaction is always a one-in-one-out virtual
147+
// transaction, so there's always just one (non-split) output.
148+
vPkt.Outputs[0].Asset.PrevWitnesses[0].TxWitness = [][]byte{
149+
[]byte("this is a signature"),
150+
}
151+
152+
return []uint32{0}, nil
153+
}
154+
155+
// A compile time check to ensure mockVirtualSigner implements the
156+
// VirtualPacketSigner interface.
157+
var _ VirtualPacketSigner = (*mockVirtualSigner)(nil)
158+
159+
// randProof returns a random proof that contains all information required for
160+
// it to be successfully serialized.
161+
func randProof(t *testing.T) proof.Proof {
162+
oddTxBlockHex, err := os.ReadFile(oddTxBlockHexFileName)
163+
require.NoError(t, err)
164+
165+
oddTxBlockBytes, err := hex.DecodeString(
166+
strings.Trim(string(oddTxBlockHex), "\n"),
167+
)
168+
require.NoError(t, err)
169+
170+
var oddTxBlock wire.MsgBlock
171+
err = oddTxBlock.Deserialize(bytes.NewReader(oddTxBlockBytes))
172+
require.NoError(t, err)
173+
174+
randGen := asset.RandGenesis(t, asset.Normal)
175+
scriptKey1 := test.RandPubKey(t)
176+
originalRandProof := proof.RandProof(
177+
t, randGen, scriptKey1, oddTxBlock, 0, 0,
178+
)
179+
180+
// Proofs don't Encode everything, so we need to do a quick Encode/
181+
// Decode cycle to make sure we can compare it afterward.
182+
proofBytes, err := proof.Encode(&originalRandProof)
183+
require.NoError(t, err)
184+
p, err := proof.Decode(proofBytes)
185+
require.NoError(t, err)
186+
187+
return *p
188+
}

0 commit comments

Comments
 (0)