Skip to content

Commit d83c907

Browse files
committed
universe: add encode/decode routines for new SignedIgnoreTuple
1 parent 807eb29 commit d83c907

File tree

3 files changed

+335
-34
lines changed

3 files changed

+335
-34
lines changed

universe/ignore_records.go

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package universe
2+
3+
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"io"
7+
8+
"github.com/btcsuite/btcd/btcec/v2"
9+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
10+
"github.com/lightninglabs/taproot-assets/asset"
11+
"github.com/lightningnetwork/lnd/tlv"
12+
)
13+
14+
type (
15+
// IgnoreTupleType is the TLV type for the tuple field in a
16+
// SignedIgnoreTuple.
17+
IgnoreTupleType = tlv.TlvType0
18+
19+
// IgnoreSignatureType is the TLV type for the signature field in a
20+
// SignedIgnoreTuple.
21+
IgnoreSignatureType = tlv.TlvType2
22+
)
23+
24+
// IgnoreTuple represents an asset previous ID that we want to ignore.
25+
type IgnoreTuple struct {
26+
asset.PrevID
27+
28+
// Amount is the amount of units that we want to ignore.
29+
Amount uint64
30+
}
31+
32+
func ignoreTupleEncoder(w io.Writer, val any, buf *[8]byte) error {
33+
if t, ok := val.(*IgnoreTuple); ok {
34+
err := asset.OutPointEncoder(w, &t.OutPoint, buf)
35+
if err != nil {
36+
return err
37+
}
38+
if err := asset.IDEncoder(w, &t.ID, buf); err != nil {
39+
return err
40+
}
41+
err = asset.SerializedKeyEncoder(w, &t.ScriptKey, buf)
42+
if err != nil {
43+
return err
44+
}
45+
46+
return tlv.EUint64(w, &t.Amount, buf)
47+
}
48+
return tlv.NewTypeForEncodingErr(val, "*PrevID")
49+
}
50+
51+
func ignoreTupleDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
52+
if typ, ok := val.(*IgnoreTuple); ok {
53+
var prevID asset.PrevID
54+
err := asset.OutPointDecoder(r, &prevID.OutPoint, buf, 0)
55+
if err != nil {
56+
return err
57+
}
58+
err = asset.IDDecoder(r, &prevID.ID, buf, sha256.Size)
59+
if err != nil {
60+
return err
61+
}
62+
if err = asset.SerializedKeyDecoder(
63+
r, &prevID.ScriptKey, buf,
64+
btcec.PubKeyBytesLenCompressed,
65+
); err != nil {
66+
return err
67+
}
68+
69+
var amt uint64
70+
if err = tlv.DUint64(r, &amt, buf, 8); err != nil {
71+
return err
72+
}
73+
74+
*typ = IgnoreTuple{
75+
PrevID: prevID,
76+
Amount: amt,
77+
}
78+
return nil
79+
}
80+
return tlv.NewTypeForDecodingErr(val, "*PrevID", l, l)
81+
}
82+
83+
// Record returns the TLV record for the IgnoreTuple.
84+
func (i *IgnoreTuple) Record() tlv.Record {
85+
const recordSize = 8 + 36 + sha256.Size + btcec.PubKeyBytesLenCompressed
86+
87+
return tlv.MakeStaticRecord(
88+
0, i, recordSize,
89+
ignoreTupleEncoder, ignoreTupleDecoder,
90+
)
91+
}
92+
93+
// IgnoreTuples is a slice of IgnoreTuple.
94+
type IgnoreTuples = []*IgnoreTuple
95+
96+
// IgnoreSignature is a Schnorr signature over an IgnoreTuple.
97+
//
98+
// TODO(roasbeef): sig validate methods, sig gen above
99+
type IgnoreSig struct {
100+
schnorr.Signature
101+
}
102+
103+
// Record returns the TLV record for the IgnoreSig.
104+
func (s *IgnoreSig) Record() tlv.Record {
105+
// Note that we set the type here as zero, as when used with a
106+
// tlv.RecordT, the type param will be used as the type.
107+
return tlv.MakeStaticRecord(
108+
0, &s.Signature, schnorr.SignatureSize,
109+
asset.SchnorrSignatureEncoder, asset.SchnorrSignatureDecoder,
110+
)
111+
}
112+
113+
// Encode serializes the IgnoreSig to the given io.Writer.
114+
func (s *IgnoreSig) Encode(w io.Writer) error {
115+
stream, err := tlv.NewStream(s.Record())
116+
if err != nil {
117+
return err
118+
}
119+
120+
return stream.Encode(w)
121+
}
122+
123+
// Decode deserializes the IgnoreSig from the given io.Reader.
124+
func (s *IgnoreSig) Decode(r io.Reader) error {
125+
stream, err := tlv.NewStream(s.Record())
126+
if err != nil {
127+
return err
128+
}
129+
return stream.Decode(r)
130+
}
131+
132+
// SignedIgnoreTuple wraps an IgnoreTuple with a signature.
133+
type SignedIgnoreTuple struct {
134+
// IgnoreTuple is the tuple that is being signed.
135+
IgnoreTuple tlv.RecordT[IgnoreTupleType, IgnoreTuple]
136+
137+
// Sig is the signature over the tuple.
138+
Sig tlv.RecordT[IgnoreSignatureType, IgnoreSig]
139+
}
140+
141+
// NewSignedIgnoreTuple creates a new SignedIgnoreTuple with the given tuple and
142+
// sig.
143+
func NewSignedIgnoreTuple(tuple IgnoreTuple, sig IgnoreSig) SignedIgnoreTuple {
144+
return SignedIgnoreTuple{
145+
IgnoreTuple: tlv.NewRecordT[IgnoreTupleType](tuple),
146+
Sig: tlv.NewRecordT[IgnoreSignatureType](sig),
147+
}
148+
}
149+
150+
// records returns the records that make up the SignedIgnoreTuple.
151+
func (s *SignedIgnoreTuple) records() []tlv.Record {
152+
return []tlv.Record{
153+
s.IgnoreTuple.Record(),
154+
s.Sig.Record(),
155+
}
156+
}
157+
158+
// Encode serializes the SignedIgnoreTuple to the given io.Writer.
159+
func (s *SignedIgnoreTuple) Encode(w io.Writer) error {
160+
tlvRecords := s.records()
161+
162+
tlvStream, err := tlv.NewStream(tlvRecords...)
163+
if err != nil {
164+
return err
165+
}
166+
167+
return tlvStream.Encode(w)
168+
}
169+
170+
// Decode deserializes the SignedIgnoreTuple from the given io.Reader.
171+
func (s *SignedIgnoreTuple) Decode(r io.Reader) error {
172+
tlvStream, err := tlv.NewStream(s.records()...)
173+
if err != nil {
174+
return err
175+
}
176+
177+
return tlvStream.Decode(r)
178+
}
179+
180+
// Bytes returns the serialized SignedIgnoreTuple record.
181+
func (s *SignedIgnoreTuple) Bytes() ([]byte, error) {
182+
var buf bytes.Buffer
183+
if err := s.Encode(&buf); err != nil {
184+
return nil, err
185+
}
186+
return buf.Bytes(), nil
187+
}
188+
189+
// DecodeSignedIgnoreTuple deserializes a SignedIgnoreTuple from the given blob.
190+
func DecodeSignedIgnoreTuple(blob []byte) (SignedIgnoreTuple, error) {
191+
var s SignedIgnoreTuple
192+
err := s.Decode(bytes.NewReader(blob))
193+
if err != nil {
194+
var s SignedIgnoreTuple
195+
return s, err
196+
}
197+
198+
return s, nil
199+
}

universe/ignore_records_test.go

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package universe
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
8+
"github.com/lightninglabs/taproot-assets/asset"
9+
"github.com/stretchr/testify/require"
10+
"pgregory.net/rapid"
11+
)
12+
13+
// IgnoreSigGen is a custom generator for generating valid IgnoreSig objects.
14+
var IgnoreSigGen = rapid.Custom(func(t *rapid.T) IgnoreSig {
15+
privKey := asset.PrivKeyInnerGen(t)
16+
17+
msg := make([]byte, 32)
18+
for i := range msg {
19+
msg[i] = rapid.Uint8().Draw(t, "msg_byte"+string(rune(i)))
20+
}
21+
22+
sig, err := schnorr.Sign(privKey, msg)
23+
if err != nil {
24+
t.Fatalf("Failed to generate signature: %v", err)
25+
}
26+
27+
return IgnoreSig{Signature: *sig}
28+
})
29+
30+
// IgnoreTupleGen is a custom generator for generating valid IgnoreTuple
31+
// (PrevID) objects. It reuses the NonGenesisPrevIDGen from the asset package.
32+
var IgnoreTupleGen = rapid.Custom(func(t *rapid.T) IgnoreTuple {
33+
return IgnoreTuple{
34+
PrevID: asset.NonGenesisPrevIDGen.Draw(t, "ignore_tuple"),
35+
Amount: rapid.Uint64().Draw(t, "amount"),
36+
}
37+
})
38+
39+
// SignedIgnoreTupleGen is a custom generator for generating valid
40+
// SignedIgnoreTuple objects.
41+
var SignedIgnoreTupleGen = rapid.Custom(func(t *rapid.T) SignedIgnoreTuple {
42+
tuple := IgnoreTupleGen.Draw(t, "tuple")
43+
sig := IgnoreSigGen.Draw(t, "sig")
44+
45+
return NewSignedIgnoreTuple(tuple, sig)
46+
})
47+
48+
// TestIgnoreSigRecordRoundTrip tests the IgnoreSig's Record method for
49+
// round-trip serialization.
50+
func TestIgnoreSigRecordRoundTrip(t *testing.T) {
51+
t.Parallel()
52+
53+
rapid.Check(t, func(t *rapid.T) {
54+
// Generate a random signature
55+
origSig := IgnoreSigGen.Draw(t, "orig_sig")
56+
57+
var buf bytes.Buffer
58+
err := origSig.Encode(&buf)
59+
require.NoError(t, err)
60+
61+
var decodedSig IgnoreSig
62+
err = decodedSig.Decode(&buf)
63+
require.NoError(t, err)
64+
65+
// Ensure the signatures are equal by comparing serialized form
66+
require.Equal(t, origSig, decodedSig)
67+
})
68+
}
69+
70+
// TestSignedIgnoreTupleRoundTrip tests the encode/decode round-trip for
71+
// SignedIgnoreTuple.
72+
func TestSignedIgnoreTupleRoundTrip(t *testing.T) {
73+
t.Parallel()
74+
75+
rapid.Check(t, func(t *rapid.T) {
76+
origTuple := SignedIgnoreTupleGen.Draw(t, "orig_tuple")
77+
78+
// Encode the tuple to bytes.
79+
tupleBytes, err := origTuple.Bytes()
80+
require.NoError(t, err)
81+
82+
// Decode the tuple from bytes.
83+
decodedTuple, err := DecodeSignedIgnoreTuple(tupleBytes)
84+
require.NoError(t, err)
85+
86+
// Check if the decoded tuple matches the original For the
87+
// PrevID
88+
require.Equal(t, origTuple, decodedTuple)
89+
})
90+
}
91+
92+
// TestSignedIgnoreTupleEncodeDecode tests encode/decode operations directly for
93+
// SignedIgnoreTuple.
94+
func TestSignedIgnoreTupleEncodeDecode(t *testing.T) {
95+
t.Parallel()
96+
rapid.Check(t, func(t *rapid.T) {
97+
// Generate a random SignedIgnoreTuple
98+
origTuple := SignedIgnoreTupleGen.Draw(t, "orig_tuple")
99+
100+
// Create a buffer and encode the tuple
101+
var buf bytes.Buffer
102+
err := origTuple.Encode(&buf)
103+
require.NoError(t, err)
104+
105+
// Create a new SignedIgnoreTuple to decode into
106+
var decodedTuple SignedIgnoreTuple
107+
108+
// Decode the tuple from the buffer
109+
err = decodedTuple.Decode(bytes.NewReader(buf.Bytes()))
110+
require.NoError(t, err)
111+
112+
// Check if the decoded tuple matches the original
113+
require.Equal(t, origTuple, decodedTuple)
114+
})
115+
}

universe/interface.go

+21-34
Original file line numberDiff line numberDiff line change
@@ -1193,28 +1193,13 @@ type Telemetry interface {
11931193
q GroupedStatsQuery) ([]*GroupedStats, error)
11941194
}
11951195

1196-
// IgnoreTuple....
1196+
// AuthenticatedIgnoreTuple wraps the existing SignedIgnoreTuple struct and
1197+
// includes information that allows it to be authenticated against an ignore
1198+
// tree universe root.
11971199
//
1198-
// TODO(roasbeef): validate method given sig
1199-
type IgnoreTuple = asset.PrevID
1200-
1201-
// IgnoreTuples...
1202-
type IgnoreTuples = []IgnoreTuple
1203-
1204-
// SignedIgnoreTuple...
1205-
//
1206-
// TODO(roasbeef): encode+decode
1207-
type SignedIgnoreTuple struct {
1208-
// IgnoreTuple...
1209-
IgnoreTuple
1210-
1211-
// Sig...
1212-
Sig schnorr.Signature
1213-
}
1214-
1215-
// AuthenticatedIgnoreTuple...
1200+
// TODO(roasbeef): supplement with bitcoin header proof
12161201
type AuthenticatedIgnoreTuple struct {
1217-
SignedIgnoreTuple
1202+
*SignedIgnoreTuple
12181203

12191204
// IgnoreTreeRoot is the root of the ignore tree that the ignore tuple
12201205
// resides within.
@@ -1225,26 +1210,28 @@ type AuthenticatedIgnoreTuple struct {
12251210
InclusionProof *mssmt.Proof
12261211
}
12271212

1228-
// TupleQueryResp...
1229-
type TupleQueryResp = lfn.Result[lfn.Option[AuthenticatedIgnoreTuple]]
1213+
// TupleQueryResp is the response to a query for ignore tuples.
1214+
type TupleQueryResp = lfn.Result[lfn.Option[[]AuthenticatedIgnoreTuple]]
12301215

1231-
// SumQueryResp...
1216+
// SumQueryResp is the response to a query for the sum of ignore tuples.
12321217
type SumQueryResp = lfn.Result[lfn.Option[uint64]]
12331218

1234-
// IgnoreTree....
1219+
// IgnoreTree represents a tree of ignore tuples which can be used to
1220+
// effectively cache rejection of invalid proofs.
12351221
type IgnoreTree interface {
1236-
// Sum...
1237-
Sum(asset.Specifier) SumQueryResp
1222+
// Sum returns the sum of the ignore tuples for the given asset.
1223+
Sum(context.Context, asset.Specifier) SumQueryResp
12381224

1239-
// AddTuple...
1225+
// AddTuple adds a new ignore tuples to the ignore tree.
12401226
//
1241-
// TODO(roasbeef): does all the signing under the hood
1242-
AddTuples(asset.Specifier,
1243-
...IgnoreTuple) lfn.Result[AuthenticatedIgnoreTuple]
1227+
// TODO(roasbeef): does all the signing under the hood?
1228+
AddTuples(context.Context, asset.Specifier,
1229+
...SignedIgnoreTuple) lfn.Result[[]AuthenticatedIgnoreTuple]
12441230

1245-
// ListTuples...
1246-
ListTuples(asset.Specifier) lfn.Result[IgnoreTuples]
1231+
// ListTuples returns the list of ignore tuples for the given asset.
1232+
ListTuples(context.Context, asset.Specifier) lfn.Result[IgnoreTuples]
12471233

1248-
// QueryTuples...
1249-
QueryTuples(asset.Specifier, IgnoreTuple) TupleQueryResp
1234+
// QueryTuples returns the ignore tuples for the given asset.
1235+
QueryTuples(context.Context, asset.Specifier,
1236+
...IgnoreTuple) TupleQueryResp
12501237
}

0 commit comments

Comments
 (0)