Skip to content

Commit d6a1cb7

Browse files
authored
Merge pull request #1587 from lightninglabs/group-key-addr-part-2
[group key addrs 7/7]: send and receive support for V2 addresses
2 parents e83909d + 8de6a71 commit d6a1cb7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+5680
-2046
lines changed

address/address.go

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/lightninglabs/taproot-assets/asset"
1818
"github.com/lightninglabs/taproot-assets/commitment"
1919
"github.com/lightninglabs/taproot-assets/fn"
20+
"github.com/lightninglabs/taproot-assets/proof"
2021
"github.com/lightninglabs/taproot-assets/taprpc"
2122
"github.com/lightningnetwork/lnd/tlv"
2223
)
@@ -48,7 +49,8 @@ var (
4849
// create a Taproot Asset address for a Normal asset with an amount of
4950
// zero.
5051
ErrInvalidAmountNormal = errors.New(
51-
"address: normal asset amount of zero",
52+
"address: zero amount cannot be used for normal asset " +
53+
"addresses of V0 or V1",
5254
)
5355

5456
// ErrUnsupportedAssetType is an error returned when we attempt to
@@ -72,6 +74,12 @@ var (
7274
// ErrUnknownVersion is returned when encountering an address with an
7375
// unrecognised version number.
7476
ErrUnknownVersion = errors.New("address: unknown version number")
77+
78+
// ErrInvalidProofCourierAddr is returned when we attempt to create a
79+
// Taproot Asset address with a proof courier address that is not valid.
80+
ErrInvalidProofCourierAddr = errors.New(
81+
"address: invalid proof courier address",
82+
)
7583
)
7684

7785
// Version denotes the version of a Taproot Asset address format.
@@ -84,8 +92,12 @@ const (
8492
// V1 addresses use V2 Taproot Asset commitments.
8593
V1 Version = 1
8694

95+
// V2 addresses support sending grouped assets and require the new
96+
// auth mailbox proof courier address format.
97+
V2 Version = 2
98+
8799
// LatestVersion is the latest supported Taproot Asset address version.
88-
latestVersion = V1
100+
latestVersion = V2
89101
)
90102

91103
// Tap represents a Taproot Asset address. Taproot Asset addresses specify an
@@ -101,16 +113,24 @@ type Tap struct {
101113
// AssetVersion is the Taproot Asset version of the asset.
102114
AssetVersion asset.Version
103115

104-
// AssetID is the asset ID of the asset.
116+
// AssetID is the asset ID of the asset. This will be all zeroes for
117+
// V2 addresses that have a group key set.
105118
AssetID asset.ID
106119

107120
// GroupKey is the tweaked public key that is used to associate assets
108121
// together across distinct asset IDs, allowing further issuance of the
109122
// asset to be made possible.
110123
GroupKey *btcec.PublicKey
111124

112-
// ScriptKey represents a tweaked Taproot output key encumbering the
113-
// different ways an asset can be spent.
125+
// ScriptKey represents the asset's Taproot output key encumbering the
126+
// different ways an asset can be spent. This is different for V2
127+
// addresses, where this key is not the Taproot output key but the
128+
// Taproot internal key (the bare/raw key) of the asset script key (not
129+
// to be confused with the InternalKey below, which is for the on-chain
130+
// part of the address). The sender will use this key to encrypt the
131+
// send fragment that they post to the proof courier's mailbox. The raw
132+
// script key will also be used by the sender to derive different
133+
// Taproot output script keys for each asset ID.
114134
ScriptKey btcec.PublicKey
115135

116136
// InternalKey is the BIP-0340/0341 public key of the receiver.
@@ -122,14 +142,25 @@ type Tap struct {
122142
TapscriptSibling *commitment.TapscriptPreimage
123143

124144
// Amount is the number of asset units being requested by the receiver.
145+
// The amount is allowed to be zero for V2 addresses, where the sender
146+
// will post a fragment containing the asset IDs and amounts to the
147+
// proof courier's mailbox.
125148
Amount uint64
126149

127150
// assetGen is the receiving asset's genesis metadata which directly
128-
// maps to its unique ID within the Taproot Asset protocol.
151+
// maps to its unique ID within the Taproot Asset protocol. For a
152+
// grouped address, this will be the genesis of the asset genesis that
153+
// started the group. This doesn't matter in the context of an address,
154+
// because currently the genesis is only used to find out the type of
155+
// asset (normal vs. collectible).
156+
// TODO(guggero): Remove this field and combine the asset ID and group
157+
// key into a single asset specifier.
129158
assetGen asset.Genesis
130159

131160
// ProofCourierAddr is the address of the proof courier that will be
132-
// used to distribute related proofs for this address.
161+
// used to distribute related proofs for this address. For V2 addresses
162+
// the proof courier address is mandatory and must be a valid auth
163+
// mailbox address.
133164
ProofCourierAddr url.URL
134165

135166
// UnknownOddTypes is a map of unknown odd types that were encountered
@@ -191,7 +222,7 @@ func New(version Version, genesis asset.Genesis, groupKey *btcec.PublicKey,
191222
}
192223

193224
case asset.Normal:
194-
if amt == 0 {
225+
if amt == 0 && version != V2 {
195226
return nil, ErrInvalidAmountNormal
196227
}
197228

@@ -208,6 +239,29 @@ func New(version Version, genesis asset.Genesis, groupKey *btcec.PublicKey,
208239
return nil, ErrUnknownVersion
209240
}
210241

242+
// Version 2 addresses behave slightly differently than V0 and V1
243+
// addresses.
244+
addressAssetID := genesis.ID()
245+
if version == V2 {
246+
// Addresses with version 2 or later must use the new
247+
// authmailbox proof courier type.
248+
if proofCourierAddr.Scheme !=
249+
proof.AuthMailboxUniRpcCourierType {
250+
251+
return nil, fmt.Errorf("%w: address version %d must "+
252+
"use the '%s' proof courier type",
253+
ErrInvalidProofCourierAddr, version,
254+
proof.AuthMailboxUniRpcCourierType)
255+
}
256+
257+
// If a group key is provided, then we zero out the asset ID in
258+
// the address, as it doesn't make sense (we'll ignore it anyway
259+
// when sending assets to this address).
260+
if groupKey != nil {
261+
addressAssetID = asset.ID{}
262+
}
263+
}
264+
211265
// We can only use a tapscript sibling that is not a Taproot Asset
212266
// commitment.
213267
if tapscriptSibling != nil {
@@ -225,7 +279,7 @@ func New(version Version, genesis asset.Genesis, groupKey *btcec.PublicKey,
225279
Version: version,
226280
ChainParams: net,
227281
AssetVersion: options.assetVersion,
228-
AssetID: genesis.ID(),
282+
AssetID: addressAssetID,
229283
GroupKey: groupKey,
230284
ScriptKey: scriptKey,
231285
InternalKey: internalKey,
@@ -259,13 +313,53 @@ func CommitmentVersion(vers Version) (*commitment.TapCommitmentVersion,
259313
// can't know without accessing all leaves of the commitment itself.
260314
case V0:
261315
return nil, nil
262-
case V1:
316+
case V1, V2:
263317
return fn.Ptr(commitment.TapCommitmentV2), nil
264318
default:
265319
return nil, ErrUnknownVersion
266320
}
267321
}
268322

323+
// ScriptKeyForAssetID returns the script key for this address for the given
324+
// asset ID. For V2 addresses, this will derive a unique script key for the
325+
// asset ID using the internal script key and a Pedersen commitment. For
326+
// addresses before V2, the script key is always the Taproot output key as
327+
// specified in the address directly.
328+
func (a *Tap) ScriptKeyForAssetID(assetID asset.ID) (*btcec.PublicKey, error) {
329+
// For addresses before V2, the script key is always the Taproot output
330+
// key as specified in the address directly.
331+
if a.Version != V2 {
332+
return &a.ScriptKey, nil
333+
}
334+
335+
// For V2 addresses, the script key is the internal key, which is used
336+
// to derive the Taproot output key for each asset ID using a unique
337+
// Pedersen commitment.
338+
scriptKey, err := asset.DeriveUniqueScriptKey(
339+
a.ScriptKey, assetID, asset.ScriptKeyDerivationUniquePedersen,
340+
)
341+
if err != nil {
342+
return nil, fmt.Errorf("unable to derive unique script key: %w",
343+
err)
344+
}
345+
346+
return scriptKey.PubKey, nil
347+
}
348+
349+
// UsesSendManifests returns true if the address requires the new authmailbox
350+
// proof courier type to transport a send manifest from the sender to the
351+
// receiver. If this is true, it means the address supports sending grouped
352+
// assets and also requires unique script keys for each asset ID.
353+
func (a *Tap) UsesSendManifests() bool {
354+
return a.Version == V2
355+
}
356+
357+
// SupportsGroupedAssets returns true if the address supports grouped assets.
358+
func (a *Tap) SupportsGroupedAssets() bool {
359+
// Only V2 addresses support grouped assets.
360+
return a.Version == V2
361+
}
362+
269363
// Net returns the ChainParams struct matching the Taproot Asset address
270364
// network.
271365
func (a *Tap) Net() (*ChainParams, error) {
@@ -336,6 +430,18 @@ func (a *Tap) TapCommitment() (*commitment.TapCommitment, error) {
336430

337431
// TaprootOutputKey returns the on-chain Taproot output key.
338432
func (a *Tap) TaprootOutputKey() (*btcec.PublicKey, error) {
433+
// V2 addresses can't be predicted on-chain, so the Taproot output key
434+
// doesn't make any sense. But because this is the primary identifier
435+
// for an address in the database, we still need to use a unique key.
436+
// We've already ensured that the script key is unique for v2 addresses,
437+
// so we can use that instead.
438+
if a.Version == V2 {
439+
// Make sure we always return the parity stripped key.
440+
return schnorr.ParsePubKey(schnorr.SerializePubKey(
441+
&a.ScriptKey,
442+
))
443+
}
444+
339445
c, err := a.TapCommitment()
340446
if err != nil {
341447
return nil, fmt.Errorf("unable to derive Taproot Asset "+
@@ -465,15 +571,16 @@ func (a *Tap) EncodeAddress() (string, error) {
465571

466572
// String returns the string representation of a Taproot Asset address.
467573
func (a *Tap) String() string {
468-
return fmt.Sprintf("TapAddr{id=%s, amount=%d, script_key=%x}",
469-
a.AssetID, a.Amount, a.ScriptKey.SerializeCompressed())
574+
s := asset.NewSpecifierOptionalGroupPubKey(a.AssetID, a.GroupKey)
575+
return fmt.Sprintf("TapAddr{specifier=%s, amount=%d, script_key=%x}",
576+
&s, a.Amount, a.ScriptKey.SerializeCompressed())
470577
}
471578

472579
// IsUnknownVersion returns true if the address version is not recognized by
473580
// this implementation of tap.
474581
func IsUnknownVersion(v Version) bool {
475582
switch v {
476-
case V0, V1:
583+
case V0, V1, V2:
477584
return false
478585
default:
479586
return true
@@ -553,6 +660,9 @@ func UnmarshalVersion(version taprpc.AddrVersion) (Version, error) {
553660
case taprpc.AddrVersion_ADDR_VERSION_V1:
554661
return V1, nil
555662

663+
case taprpc.AddrVersion_ADDR_VERSION_V2:
664+
return V2, nil
665+
556666
default:
557667
return 0, fmt.Errorf("unknown address version: %v", version)
558668
}
@@ -570,6 +680,9 @@ func MarshalVersion(version Version) (taprpc.AddrVersion, error) {
570680
case V1:
571681
return taprpc.AddrVersion_ADDR_VERSION_V1, nil
572682

683+
case V2:
684+
return taprpc.AddrVersion_ADDR_VERSION_V2, nil
685+
573686
default:
574687
return 0, fmt.Errorf("unknown address version: %v", version)
575688
}

0 commit comments

Comments
 (0)