@@ -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)
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
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.
271365func (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.
338432func (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.
467573func (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.
474581func 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