Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tap address to VOutput #468

Closed
wants to merge 9 commits into from
87 changes: 69 additions & 18 deletions address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,11 @@ func (a *Tap) TaprootOutputKey() (*btcec.PublicKey, error) {
return taprootOutputKey, nil
}

// EncodeRecords determines the non-nil records to include when encoding an
// address at runtime.
func (a *Tap) EncodeRecords() []tlv.Record {
// EncodeBech32mRecords determines the non-nil records to include when encoding
// an address with bech32m format at runtime.
func (a *Tap) EncodeBech32mRecords() []tlv.Record {
records := make([]tlv.Record, 0, 6)
records = append(records, newAddressVersionRecord(&a.AssetVersion))
records = append(records, newAddressAssetVersionRecord(&a.AssetVersion))
records = append(records, newAddressAssetID(&a.AssetID))

if a.GroupKey != nil {
Expand All @@ -328,11 +328,11 @@ func (a *Tap) EncodeRecords() []tlv.Record {
return records
}

// DecodeRecords provides all records known for an address for proper
// decoding.
func (a *Tap) DecodeRecords() []tlv.Record {
// DecodeBech32mRecords provides all TLV records for decoding a bech32m encoded
// address.
func (a *Tap) DecodeBech32mRecords() []tlv.Record {
return []tlv.Record{
newAddressVersionRecord(&a.AssetVersion),
newAddressAssetVersionRecord(&a.AssetVersion),
newAddressAssetID(&a.AssetID),
newAddressGroupKeyRecord(&a.GroupKey),
newAddressScriptKeyRecord(&a.ScriptKey),
Expand All @@ -343,28 +343,49 @@ func (a *Tap) DecodeRecords() []tlv.Record {
}
}

// Encode encodes an address into a TLV stream.
// EncodeBech32mToWriter encodes an address into a given writer targeted towards
// the bech32m address encoding.
func (a *Tap) EncodeBech32mToWriter(w io.Writer) error {
stream, err := tlv.NewStream(a.EncodeBech32mRecords()...)
if err != nil {
return err
}
return stream.Encode(w)
}

// DecodeBech32mToReader decodes a bech32m encoded address from a TLV stream.
func (a *Tap) DecodeBech32mToReader(r io.Reader) error {
stream, err := tlv.NewStream(a.DecodeBech32mRecords()...)
if err != nil {
return err
}
return stream.Decode(r)
}

// Encode encodes an address into a given writer.
func (a *Tap) Encode(w io.Writer) error {
stream, err := tlv.NewStream(a.EncodeRecords()...)
stream, err := tlv.NewStream(a.EncodeAllRecords()...)
if err != nil {
return err
}
return stream.Encode(w)
}

// Decode decodes an address from a TLV stream.
// Decode decodes an encoded address from a TLV stream.
func (a *Tap) Decode(r io.Reader) error {
stream, err := tlv.NewStream(a.DecodeRecords()...)
stream, err := tlv.NewStream(a.DecodeAllRecords()...)
if err != nil {
return err
}
return stream.Decode(r)
}

// EncodeAddress returns a bech32m string encoding of a Taproot Asset address.
func (a *Tap) EncodeAddress() (string, error) {
// EncodeBech32m returns a bech32m string encoding of a Taproot Asset address.
//
// Example bech32m encoded address: taprt1qqqsqq3q0gupzcctkv6s83jndsazy0fu4m9...
func (a *Tap) EncodeBech32m() (string, error) {
var buf bytes.Buffer
if err := a.Encode(&buf); err != nil {
if err := a.EncodeBech32mToWriter(&buf); err != nil {
return "", err
}

Expand All @@ -387,15 +408,45 @@ func (a *Tap) EncodeAddress() (string, error) {
return "", ErrUnsupportedHRP
}

// EncodeAllRecords returns TLV records for encoding every non-nil field.
//
// TODO(ffranr): Add record for ChainParams field.
func (a *Tap) EncodeAllRecords() []tlv.Record {
records := a.EncodeBech32mRecords()

// Add records which are absent from the bech32m encoding.
// When encoding, we only include non-nil fields.
if a.groupSig != nil {
records = append(records, newAddressGroupSigRecord(&a.groupSig))
}

records = append(records, newAssetGenesisRecord(&a.assetGen))

return records
}

// DecodeAllRecords returns TLV records for decoding every field.
//
// TODO(ffranr): Add record for ChainParams field.
func (a *Tap) DecodeAllRecords() []tlv.Record {
records := a.DecodeBech32mRecords()

// Add records which are absent from the bech32m encoding.
records = append(records, newAddressGroupSigRecord(&a.groupSig))
records = append(records, newAssetGenesisRecord(&a.assetGen))
return records
}

// String returns the string representation of a Taproot Asset address.
func (a *Tap) String() string {
return fmt.Sprintf("TapAddr{id=%s, amount=%d, script_key=%x}",
a.AssetID, a.Amount, a.ScriptKey.SerializeCompressed())
}

// DecodeAddress parses a bech32m encoded Taproot Asset address string and
// DecodeBech32m parses a bech32m encoded Taproot Asset address string and
// returns the HRP and address TLV.
func DecodeAddress(addr string, net *ChainParams) (*Tap, error) {
// Example bech32m encoded address: taprt1qqqsqq3q0gupzcctkv6s83jndsazy0fu4m9...
func DecodeBech32m(addr string, net *ChainParams) (*Tap, error) {
// Bech32m encoded Taproot Asset addresses start with a human-readable
// part (hrp) followed by '1'. For Bitcoin mainnet the hrp is "tap",
// and for testnet it is "tapt". If the address string has a prefix
Expand Down Expand Up @@ -437,7 +488,7 @@ func DecodeAddress(addr string, net *ChainParams) (*Tap, error) {

var a Tap
buf := bytes.NewBuffer(converted)
if err := a.Decode(buf); err != nil {
if err := a.DecodeBech32mToReader(buf); err != nil {
return nil, err
}

Expand Down
16 changes: 8 additions & 8 deletions address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func randEncodedAddress(t *testing.T, net *ChainParams, groupPubKey,
return nil, "", err
}

encodedAddr, err := newAddr.EncodeAddress()
encodedAddr, err := newAddr.EncodeBech32m()

return newAddr, encodedAddr, err
}
Expand Down Expand Up @@ -202,11 +202,11 @@ func TestAddressEncoding(t *testing.T) {
t.Helper()

assertAddressEqual(t, a, a.Copy())
addr, err := a.EncodeAddress()
addr, err := a.EncodeBech32m()
require.NoError(t, err)
net, err := a.Net()
require.NoError(t, err)
b, err := DecodeAddress(addr, net)
b, err := DecodeBech32m(addr, net)
require.NoError(t, err)
assertAddressEqual(t, a, b)

Expand Down Expand Up @@ -311,7 +311,7 @@ func TestAddressEncoding(t *testing.T) {
t, &TestNet3Tap, true, false,
asset.Collectible,
)
_, err := DecodeAddress(
_, err := DecodeBech32m(
encodedAddr, &MainNetTap,
)
return newAddr, "", err
Expand All @@ -326,7 +326,7 @@ func TestAddressEncoding(t *testing.T) {
asset.Collectible,
)
encodedAddr = encodedAddr[4:]
_, err := DecodeAddress(
_, err := DecodeBech32m(
encodedAddr[4:], &TestNet3Tap,
)
return newAddr, "", err
Expand Down Expand Up @@ -383,7 +383,7 @@ func runBIPTestVector(t *testing.T, testVectors *TestVectors) {

a := validCase.Address.ToAddress(tt)

addrString, err := a.EncodeAddress()
addrString, err := a.EncodeBech32m()
require.NoError(tt, err)

areEqual := validCase.Expected == addrString
Expand All @@ -393,7 +393,7 @@ func runBIPTestVector(t *testing.T, testVectors *TestVectors) {
chainParams, err := a.Net()
require.NoError(tt, err)

expectedAddress, err := DecodeAddress(
expectedAddress, err := DecodeBech32m(
validCase.Expected, chainParams,
)
require.NoError(tt, err)
Expand All @@ -412,7 +412,7 @@ func runBIPTestVector(t *testing.T, testVectors *TestVectors) {
chainParams, err := a.Net()
require.NoError(tt, err)

decoded, err := DecodeAddress(
decoded, err := DecodeBech32m(
validCase.Expected, chainParams,
)
require.NoError(tt, err)
Expand Down
33 changes: 33 additions & 0 deletions address/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/lightningnetwork/lnd/tlv"
)

Expand Down Expand Up @@ -72,3 +73,35 @@ func urlDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
val, "*url.URL", l, l,
)
}

// schnorrSigEncoder encodes a schnorr.Signature as a variable length byte
// slice.
func schnorrSigEncoder(w io.Writer, val any, buf *[8]byte) error {
if s, ok := val.(**schnorr.Signature); ok {
sigBytes := (*s).Serialize()
return tlv.EVarBytes(w, &sigBytes, buf)
}
return tlv.NewTypeForEncodingErr(val, "*schnorr.Signature")
}

// urlDecoder decodes a variable length byte slice as a schnorr.Signature.
func schnorrSigDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
if s, ok := val.(**schnorr.Signature); ok {
var sigBytes []byte
err := tlv.DVarBytes(r, &sigBytes, buf, l)
if err != nil {
return err
}

sig, err := schnorr.ParseSignature(sigBytes)
if err != nil {
return err
}
*s = sig

return nil
}
return tlv.NewTypeForDecodingErr(
val, "*schnorr.Signature", l, l,
)
}
4 changes: 4 additions & 0 deletions address/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ type TestVectors struct {
func NewTestFromAddress(t testing.TB, a *Tap) *TestAddress {
t.Helper()

if a == nil {
return nil
}

ta := &TestAddress{
ChainParamsHRP: a.ChainParams.TapHRP,
AssetVersion: uint8(a.AssetVersion),
Expand Down
47 changes: 46 additions & 1 deletion address/records.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package address

import (
"bytes"
"net/url"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightningnetwork/lnd/tlv"
Expand Down Expand Up @@ -37,9 +39,22 @@ const (

// addrProofCourierType is the TLV type of the proof courier address.
addrProofCourierAddrType addressTLVType = 10

// addrGroupSigType is the TLV type of the signature of the asset
// genesis.
//
// NOTE: This field is optional. It is not included in the bech32m encoding
// of the address.
addrGroupSigType addressTLVType = 11

// addrAssetGenType is the TLV type of the asset's genesis metadata.
//
// NOTE: This field is optional. It is not included in the bech32m encoding
// of the address.
addrAssetGenType addressTLVType = 13
)

func newAddressVersionRecord(version *asset.Version) tlv.Record {
func newAddressAssetVersionRecord(version *asset.Version) tlv.Record {
return tlv.MakeStaticRecord(
addrVersionType, version, 1, asset.VersionEncoder,
asset.VersionDecoder,
Expand All @@ -59,6 +74,19 @@ func newAddressGroupKeyRecord(groupKey **btcec.PublicKey) tlv.Record {
)
}

func newAddressGroupSigRecord(groupSig **schnorr.Signature) tlv.Record {
var addrBytes []byte
if *groupSig != nil {
addrBytes = (*groupSig).Serialize()
}
recordSize := tlv.SizeVarBytes(&addrBytes)

return tlv.MakeDynamicRecord(
addrGroupSigType, groupSig, recordSize,
schnorrSigEncoder, schnorrSigDecoder,
)
}

func newAddressScriptKeyRecord(scriptKey *btcec.PublicKey) tlv.Record {
return tlv.MakeStaticRecord(
addrScriptKeyType, scriptKey, btcec.PubKeyBytesLenCompressed,
Expand Down Expand Up @@ -97,6 +125,23 @@ func newAddressAmountRecord(amount *uint64) tlv.Record {
)
}

func newAssetGenesisRecord(genesis *asset.Genesis) tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := asset.GenesisEncoder(&b, genesis, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
addrAssetGenType, genesis, recordSize,
asset.GenesisEncoder, asset.GenesisDecoder,
)
}

func newProofCourierAddrRecord(addr *url.URL) tlv.Record {
var addrBytes []byte
if addr != nil {
Expand Down
10 changes: 5 additions & 5 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ func (r *rpcServer) DecodeAddr(_ context.Context,

tapParams := address.ParamsForChain(r.cfg.ChainParams.Name)

addr, err := address.DecodeAddress(in.Addr, &tapParams)
addr, err := address.DecodeBech32m(in.Addr, &tapParams)
if err != nil {
return nil, fmt.Errorf("unable to decode addr: %w", err)
}
Expand Down Expand Up @@ -1389,7 +1389,7 @@ func (r *rpcServer) AddrReceives(ctx context.Context,
if len(in.FilterAddr) > 0 {
tapParams := address.ParamsForChain(r.cfg.ChainParams.Name)

addr, err := address.DecodeAddress(in.FilterAddr, &tapParams)
addr, err := address.DecodeBech32m(in.FilterAddr, &tapParams)
if err != nil {
return nil, fmt.Errorf("unable to decode addr: %w", err)
}
Expand Down Expand Up @@ -1511,7 +1511,7 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
err error
)
for a := range raw.Recipients {
addr, err = address.DecodeAddress(a, &tapParams)
addr, err = address.DecodeBech32m(a, &tapParams)
if err != nil {
return nil, fmt.Errorf("unable to decode "+
"addr: %w", err)
Expand Down Expand Up @@ -1690,7 +1690,7 @@ func (r *rpcServer) NextScriptKey(ctx context.Context,
func marshalAddr(addr *address.Tap,
db address.Storage) (*taprpc.Addr, error) {

addrStr, err := addr.EncodeAddress()
addrStr, err := addr.EncodeBech32m()
if err != nil {
return nil, fmt.Errorf("unable to encode addr: %w", err)
}
Expand Down Expand Up @@ -1843,7 +1843,7 @@ func (r *rpcServer) SendAsset(_ context.Context,
return nil, fmt.Errorf("addr %d must be specified", idx)
}

tapAddrs[idx], err = address.DecodeAddress(
tapAddrs[idx], err = address.DecodeBech32m(
in.TapAddrs[idx], &tapParams,
)
if err != nil {
Expand Down
Loading