Skip to content

Commit 4e78fb2

Browse files
committed
commitment: make forward-compatible by parsing unknown odd types
1 parent 7b533ae commit 4e78fb2

File tree

6 files changed

+367
-86
lines changed

6 files changed

+367
-86
lines changed

commitment/commitment_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package commitment
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/hex"
67
"math/rand"
@@ -16,6 +17,7 @@ import (
1617
"github.com/lightninglabs/taproot-assets/internal/test"
1718
"github.com/lightninglabs/taproot-assets/mssmt"
1819
"github.com/lightningnetwork/lnd/keychain"
20+
"github.com/lightningnetwork/lnd/tlv"
1921
"github.com/stretchr/testify/require"
2022
)
2123

@@ -1634,3 +1636,160 @@ func TestAssetCommitmentDeleteMaxVersion(t *testing.T) {
16341636

16351637
require.Equal(t, asset.V0, assetCommitment.Version)
16361638
}
1639+
1640+
// TestProofUnknownOddType tests that an unknown odd type is allowed in a
1641+
// commitment proof and that we can still arrive at the correct root hash with
1642+
// it.
1643+
func TestProofUnknownOddType(t *testing.T) {
1644+
genesis := asset.RandGenesis(t, asset.Normal)
1645+
asset1 := randAsset(t, genesis, nil)
1646+
1647+
singleCommitment, err := FromAssets(nil, asset1)
1648+
require.NoError(t, err)
1649+
1650+
_, knownProof, err := singleCommitment.Proof(
1651+
asset1.TapCommitmentKey(), asset1.AssetCommitmentKey(),
1652+
)
1653+
require.NoError(t, err)
1654+
1655+
var knownProofBytes []byte
1656+
test.RunUnknownOddTypeTest(
1657+
t, knownProof, &asset.ErrUnknownType{},
1658+
func(buf *bytes.Buffer, proof *Proof) error {
1659+
err := proof.Encode(buf)
1660+
1661+
knownProofBytes = fn.CopySlice(buf.Bytes())
1662+
1663+
return err
1664+
},
1665+
func(buf *bytes.Buffer) (*Proof, error) {
1666+
var parsedProof Proof
1667+
return &parsedProof, parsedProof.Decode(buf)
1668+
},
1669+
func(parsedProof *Proof, unknownTypes tlv.TypeMap) {
1670+
require.Equal(
1671+
t, unknownTypes, parsedProof.UnknownOddTypes,
1672+
)
1673+
1674+
// The proof should've changed, to make sure the
1675+
// unknown value was taken into account when creating
1676+
// the serialized proof.
1677+
var newBuf bytes.Buffer
1678+
err = parsedProof.Encode(&newBuf)
1679+
require.NoError(t, err)
1680+
1681+
require.NotEqual(t, knownProofBytes, newBuf.Bytes())
1682+
1683+
parsedProof.UnknownOddTypes = nil
1684+
require.Equal(t, knownProof, parsedProof)
1685+
},
1686+
)
1687+
}
1688+
1689+
// TestAssetProofUnknownOddType tests that an unknown odd type is allowed in an
1690+
// asset proof and that we can still arrive at the correct root hash with it.
1691+
func TestAssetProofUnknownOddType(t *testing.T) {
1692+
genesis := asset.RandGenesis(t, asset.Normal)
1693+
asset1 := randAsset(t, genesis, nil)
1694+
1695+
singleCommitment, err := FromAssets(nil, asset1)
1696+
require.NoError(t, err)
1697+
1698+
_, commitmentProof, err := singleCommitment.Proof(
1699+
asset1.TapCommitmentKey(), asset1.AssetCommitmentKey(),
1700+
)
1701+
require.NoError(t, err)
1702+
1703+
require.NotNil(t, commitmentProof.AssetProof)
1704+
knownAssetProof := commitmentProof.AssetProof
1705+
1706+
var knownProofBytes []byte
1707+
test.RunUnknownOddTypeTest(
1708+
t, knownAssetProof, &asset.ErrUnknownType{},
1709+
func(buf *bytes.Buffer, proof *AssetProof) error {
1710+
err := AssetProofEncoder(buf, &proof, nil)
1711+
1712+
knownProofBytes = fn.CopySlice(buf.Bytes())
1713+
1714+
return err
1715+
},
1716+
func(buf *bytes.Buffer) (*AssetProof, error) {
1717+
parsedProof := &AssetProof{}
1718+
return parsedProof, AssetProofDecoder(
1719+
buf, &parsedProof, nil, uint64(buf.Len()),
1720+
)
1721+
},
1722+
func(parsedProof *AssetProof, unknownTypes tlv.TypeMap) {
1723+
require.Equal(
1724+
t, unknownTypes, parsedProof.UnknownOddTypes,
1725+
)
1726+
1727+
// The proof should've changed, to make sure the unknown
1728+
// value was taken into account when creating the
1729+
// serialized proof.
1730+
var newBuf bytes.Buffer
1731+
err = AssetProofEncoder(&newBuf, &parsedProof, nil)
1732+
require.NoError(t, err)
1733+
1734+
require.NotEqual(t, knownProofBytes, newBuf.Bytes())
1735+
1736+
parsedProof.UnknownOddTypes = nil
1737+
require.Equal(t, knownAssetProof, parsedProof)
1738+
},
1739+
)
1740+
}
1741+
1742+
// TestTaprootAssetProofUnknownOddType tests that an unknown odd type is allowed
1743+
// in a Taproot asset proof and that we can still arrive at the correct root
1744+
// hash with it.
1745+
func TestTaprootAssetProofUnknownOddType(t *testing.T) {
1746+
genesis := asset.RandGenesis(t, asset.Normal)
1747+
asset1 := randAsset(t, genesis, nil)
1748+
1749+
singleCommitment, err := FromAssets(nil, asset1)
1750+
require.NoError(t, err)
1751+
1752+
_, commitmentProof, err := singleCommitment.Proof(
1753+
asset1.TapCommitmentKey(), asset1.AssetCommitmentKey(),
1754+
)
1755+
require.NoError(t, err)
1756+
1757+
knownTaprootAssetProof := commitmentProof.TaprootAssetProof
1758+
1759+
var knownProofBytes []byte
1760+
test.RunUnknownOddTypeTest(
1761+
t, knownTaprootAssetProof, &asset.ErrUnknownType{},
1762+
func(buf *bytes.Buffer, proof TaprootAssetProof) error {
1763+
err := TaprootAssetProofEncoder(buf, &proof, nil)
1764+
1765+
knownProofBytes = fn.CopySlice(buf.Bytes())
1766+
1767+
return err
1768+
},
1769+
func(buf *bytes.Buffer) (TaprootAssetProof, error) {
1770+
var parsedProof TaprootAssetProof
1771+
return parsedProof, TaprootAssetProofDecoder(
1772+
buf, &parsedProof, nil, uint64(buf.Len()),
1773+
)
1774+
},
1775+
func(parsedProof TaprootAssetProof, unknownTypes tlv.TypeMap) {
1776+
require.Equal(
1777+
t, unknownTypes, parsedProof.UnknownOddTypes,
1778+
)
1779+
1780+
// The proof should've changed, to make sure the unknown
1781+
// value was taken into account when creating the
1782+
// serialized proof.
1783+
var newBuf bytes.Buffer
1784+
err = TaprootAssetProofEncoder(
1785+
&newBuf, &parsedProof, nil,
1786+
)
1787+
require.NoError(t, err)
1788+
1789+
require.NotEqual(t, knownProofBytes, newBuf.Bytes())
1790+
1791+
parsedProof.UnknownOddTypes = nil
1792+
require.Equal(t, knownTaprootAssetProof, parsedProof)
1793+
},
1794+
)
1795+
}

commitment/encoding.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ func TapCommitmentVersionDecoder(r io.Reader, val any, buf *[8]byte,
3232

3333
func AssetProofEncoder(w io.Writer, val any, buf *[8]byte) error {
3434
if t, ok := val.(**AssetProof); ok {
35-
stream, err := tlv.NewStream((*t).Records()...)
35+
records := asset.CombineRecords(
36+
(*t).Records(), (*t).UnknownOddTypes,
37+
)
38+
stream, err := tlv.NewStream(records...)
3639
if err != nil {
3740
return err
3841
}
@@ -59,15 +62,16 @@ func AssetProofDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
5962
return err
6063
}
6164

62-
// TODO(guggero): Store unknown types (next commits).
63-
_, err = asset.TlvStrictDecodeP2P(
65+
unknownOddTypes, err := asset.TlvStrictDecodeP2P(
6466
stream, bytes.NewReader(streamBytes),
6567
KnownAssetProofTypes,
6668
)
6769
if err != nil {
6870
return err
6971
}
7072

73+
proof.UnknownOddTypes = unknownOddTypes
74+
7175
*typ = &proof
7276
return nil
7377
}
@@ -76,7 +80,10 @@ func AssetProofDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
7680

7781
func TaprootAssetProofEncoder(w io.Writer, val any, buf *[8]byte) error {
7882
if t, ok := val.(*TaprootAssetProof); ok {
79-
stream, err := tlv.NewStream((*t).Records()...)
83+
records := asset.CombineRecords(
84+
(*t).Records(), (*t).UnknownOddTypes,
85+
)
86+
stream, err := tlv.NewStream(records...)
8087
if err != nil {
8188
return err
8289
}
@@ -105,15 +112,16 @@ func TaprootAssetProofDecoder(r io.Reader, val any, buf *[8]byte,
105112
return err
106113
}
107114

108-
// TODO(guggero): Store unknown types (next commits).
109-
_, err = asset.TlvStrictDecodeP2P(
115+
unknownOddTypes, err := asset.TlvStrictDecodeP2P(
110116
stream, bytes.NewReader(streamBytes),
111117
KnownTaprootAssetProofTypes,
112118
)
113119
if err != nil {
114120
return err
115121
}
116122

123+
proof.UnknownOddTypes = unknownOddTypes
124+
117125
*typ = proof
118126
return nil
119127
}

commitment/mock.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/lightninglabs/taproot-assets/asset"
1212
"github.com/lightninglabs/taproot-assets/internal/test"
1313
"github.com/lightninglabs/taproot-assets/mssmt"
14+
"github.com/lightningnetwork/lnd/tlv"
1415
"github.com/stretchr/testify/require"
1516
"golang.org/x/exp/maps"
1617
)
@@ -75,15 +76,21 @@ func NewTestFromProof(t testing.TB, p *Proof) *TestProof {
7576

7677
tp := &TestProof{
7778
TaprootAssetProof: &TestTaprootAssetProof{
78-
Proof: mssmt.HexProof(t, &p.TaprootAssetProof.Proof),
79-
Version: uint8(p.TaprootAssetProof.Version),
79+
Proof: mssmt.HexProof(
80+
t, &p.TaprootAssetProof.Proof,
81+
),
82+
Version: uint8(p.TaprootAssetProof.Version),
83+
UnknownOddTypes: p.UnknownOddTypes,
8084
},
8185
}
8286
if p.AssetProof != nil {
8387
tp.AssetProof = &TestAssetProof{
8488
Proof: mssmt.HexProof(t, &p.AssetProof.Proof),
8589
Version: uint8(p.AssetProof.Version),
86-
TapKey: hex.EncodeToString(p.AssetProof.TapKey[:]),
90+
TapKey: hex.EncodeToString(
91+
p.AssetProof.TapKey[:],
92+
),
93+
UnknownOddTypes: p.UnknownOddTypes,
8794
}
8895
}
8996

@@ -93,6 +100,7 @@ func NewTestFromProof(t testing.TB, p *Proof) *TestProof {
93100
type TestProof struct {
94101
AssetProof *TestAssetProof `json:"asset_proof"`
95102
TaprootAssetProof *TestTaprootAssetProof `json:"taproot_asset_proof"`
103+
UnknownOddTypes tlv.TypeMap `json:"unknown_odd_types"`
96104
}
97105

98106
func (tp *TestProof) ToProof(t testing.TB) *Proof {
@@ -106,12 +114,17 @@ func (tp *TestProof) ToProof(t testing.TB) *Proof {
106114
Version: TapCommitmentVersion(
107115
tp.TaprootAssetProof.Version,
108116
),
117+
UnknownOddTypes: tp.TaprootAssetProof.UnknownOddTypes,
109118
},
119+
UnknownOddTypes: tp.UnknownOddTypes,
110120
}
111121
if tp.AssetProof != nil {
112122
p.AssetProof = &AssetProof{
113-
Proof: mssmt.ParseProof(t, tp.AssetProof.Proof),
114-
Version: asset.Version(tp.AssetProof.Version),
123+
Proof: mssmt.ParseProof(
124+
t, tp.AssetProof.Proof,
125+
),
126+
Version: asset.Version(tp.AssetProof.Version),
127+
UnknownOddTypes: tp.AssetProof.UnknownOddTypes,
115128
}
116129
assetID, err := hex.DecodeString(tp.AssetProof.TapKey)
117130
require.NoError(t, err)
@@ -122,14 +135,16 @@ func (tp *TestProof) ToProof(t testing.TB) *Proof {
122135
}
123136

124137
type TestAssetProof struct {
125-
Proof string `json:"proof"`
126-
Version uint8 `json:"version"`
127-
TapKey string `json:"tap_key"`
138+
Proof string `json:"proof"`
139+
Version uint8 `json:"version"`
140+
TapKey string `json:"tap_key"`
141+
UnknownOddTypes tlv.TypeMap `json:"unknown_odd_types"`
128142
}
129143

130144
type TestTaprootAssetProof struct {
131-
Proof string `json:"proof"`
132-
Version uint8 `json:"version"`
145+
Proof string `json:"proof"`
146+
Version uint8 `json:"version"`
147+
UnknownOddTypes tlv.TypeMap `json:"unknown_odd_types"`
133148
}
134149

135150
func NewTestFromSplitSet(t testing.TB, s SplitSet) TestSplitSet {

commitment/proof.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ type AssetProof struct {
2929
// committed asset must match, otherwise an asset.GroupKey which every
3030
// committed asset must match.
3131
TapKey [32]byte
32+
33+
// UnknownOddTypes is a map of unknown odd types that were encountered
34+
// during decoding. This map is used to preserve unknown types that we
35+
// don't know of yet, so we can still encode them back when serializing.
36+
// This enables forward compatibility with future versions of the
37+
// protocol as it allows new odd (optional) types to be added without
38+
// breaking old clients that don't yet fully understand them.
39+
UnknownOddTypes tlv.TypeMap
3240
}
3341

3442
// Records returns the encoding/decoding records for the AssetProof.
@@ -47,6 +55,14 @@ type TaprootAssetProof struct {
4755

4856
// Version is the version of the TapCommitment used to create the proof.
4957
Version TapCommitmentVersion
58+
59+
// UnknownOddTypes is a map of unknown odd types that were encountered
60+
// during decoding. This map is used to preserve unknown types that we
61+
// don't know of yet, so we can still encode them back when serializing.
62+
// This enables forward compatibility with future versions of the
63+
// protocol as it allows new odd (optional) types to be added without
64+
// breaking old clients that don't yet fully understand them.
65+
UnknownOddTypes tlv.TypeMap
5066
}
5167

5268
// Records returns the encoding/decoding records for the TaprootAssetProof.
@@ -72,6 +88,14 @@ type Proof struct {
7288
// TaprootAssetProof is the proof used along with the asset commitment
7389
// to arrive at the root of the TapCommitment MS-SMT.
7490
TaprootAssetProof TaprootAssetProof
91+
92+
// UnknownOddTypes is a map of unknown odd types that were encountered
93+
// during decoding. This map is used to preserve unknown types that we
94+
// don't know of yet, so we can still encode them back when serializing.
95+
// This enables forward compatibility with future versions of the
96+
// protocol as it allows new odd (optional) types to be added without
97+
// breaking old clients that don't yet fully understand them.
98+
UnknownOddTypes tlv.TypeMap
7599
}
76100

77101
// EncodeRecords returns the encoding records for the Proof.
@@ -83,7 +107,9 @@ func (p Proof) EncodeRecords() []tlv.Record {
83107
records = append(
84108
records, ProofTaprootAssetProofRecord(&p.TaprootAssetProof),
85109
)
86-
return records
110+
111+
// Add any unknown odd types that were encountered during decoding.
112+
return asset.CombineRecords(records, p.UnknownOddTypes)
87113
}
88114

89115
// DecodeRecords returns the decoding records for the CommitmentProof.
@@ -110,9 +136,16 @@ func (p *Proof) Decode(r io.Reader) error {
110136
return err
111137
}
112138

113-
// TODO(guggero): Store unknown types (next commits).
114-
_, err = asset.TlvStrictDecodeP2P(stream, r, KnownProofTypes)
115-
return err
139+
unknownOddTypes, err := asset.TlvStrictDecodeP2P(
140+
stream, r, KnownProofTypes,
141+
)
142+
if err != nil {
143+
return err
144+
}
145+
146+
p.UnknownOddTypes = unknownOddTypes
147+
148+
return nil
116149
}
117150

118151
// DeriveByAssetInclusion derives the Taproot Asset commitment containing the

0 commit comments

Comments
 (0)