BIP: ??? Layer: Applications Title: Taproot Asset On Chain Addresses Author: Olaoluwa Osuntokun <laolu32@gmail.com> Comments-Summary: No comments yet. Comments-URI: https://git Status: Draft Type: Standards Track Created: 2021-12-10 License: BSD-2-Clause
This document describes a way to map a single-asset Taproot Asset send to a
familiar bech32m
address, as well as a way to map that address into
a valid Taproot Asset script tree that can be included in a broadcast
transaction to complete a transfer.
Once the transaction has been broadcast, the receiver can use the
previous outpoint of the confirmed transaction to lookup the complete asset
proof in their chosen Universe.
This document is licensed under the 2-clause BSD license.
The Taproot Asset protocol needs an easy way to allow users to send each other
assets on-chain, without requiring several rounds of interaction to exchange and
validate proofs. By using the existing bech32m
address
serialization standard, such addresses look distinct, while also looking
familiar enough based on the character set encoding. The described address
format also addresses a number of possible foot guns, by making it impossible
to send the wrong asset (based on an address) amongst other protections.
A Taproot Asset is uniquely defined by its asset_genesis
as well as
the asset_script_key
that serves as a predicate that must be
satisfied for transfers. These values, along with an internal Taproot key used
when creating the Bitcoin output that holds the Taproot Asset, are encoded into
a single address.
Let the human readable prefix (as specified by BIP 173) be:
tapbc
for mainnettaptb
for testnettaprt
for regtesttaptb
for the public signettapsb
for simnet
taproot_asset_hrp
Given the 32-byte asset_id
, 33-byte compressed
asset_script_key
, and 33-byte compressed internal public
key, 8-byte amount to send, an address is encoded as:
bech32m(hrp=taproot_asset_hrp, addr_tlv_payload)
addr_tlv_payload
is a TLV payload composed of the following
types:
- type: 0 (
taproot_asset_version
)- value:
- [
u8
:version
]
- [
- value:
- type: 2 (
asset_id
)- value:
- [
32*byte
:asset_id
]
- [
- value:
- type: 3 (
asset_key_family
)- value:
- [
33*byte
:family_key
]
- [
- value:
- type: 4 (
asset_script_key
)- value:
- [
33*byte
:script_key
]
- [
- value:
- type: 6 (
internal_key
)- value:
- [
33*byte
:taproot_internal_key
]
- [
- value:
- type: 7 (
taproot_sibling_preimage
)- value:
- [
...*byte
:tapscript_preimage
]
- [
- value:
- type: 8 (
amt
)- value:
- [
BigSize
:amt_to_send
]
- [
- value:
- type: 10 (
proof_courier_addr
)- value:
- [
...*byte
:proof_courier_addr
]
- [
- value:
The only odd keys specified in the current version are the
asset_key_family
type and the asset_type
field. The
asset_key_family
field isn't always needed for assets that don't
allow for continual re-issuance. Similarly, if the asset_type
field isn't specified, then one can assume a normal asset is being sent.
The proof_courier_addr
is a mandatory URI (RFC 3986) that indicates
what proof courier to use when sending the proofs from the sender to the
recipient. The scheme (protocol) indicates the type of courier transport to use,
current valid values are hashmail://
for Hashmail based couriers
and universerpc://
for gRPC based transfer via a universe server.
Given a valid Taproot Asset address, decompose the contents into the referenced
asset_id
, asset_script_key
, and
internal_key
. Look up the full asset_genesis
with the
asset_id
in the appropriate Universe.
Construct a new blank Taproot Asset leaf according to the default Asset Leaf Format with the following values being set explicitly (and all other values being their default/zero values):
taproot_asset_version
:0
asset_genesis
:asset_genesis
amt
:amt_to_send
asset_script_version
:0
asset_script_key
:asset_script_key
asset_key_family
:asset_key_family
0x0c
with the
sole leaf being the serialized TLV blob specified above.
Create the top-level taproot public key script, as a segwit v1 witness program, as specified in BIP 341, using the included key as the internal key.
With the target taproot public key script constructed, the asset is sent to the receiver with the execution of the following steps:
- Construct a valid transaction that spends an input that holds the referenced
asset_id
and exactlyamt
units of the asset. - Create a new Taproot Asset output commitment based on the input commitment (this will be the change output), that now only commits to
S-A
units ofasset_id
, whereS
is the input amount, andA
is the amount specified in the encoded Taproot Asset address.- This new leaf MUST have a
split_commitment
specified that commits to the position (keyed bysha256(output_index || asset_id || asset_script_key)
within the transaction of the newly created asset leaf for the receiver. This split commitment is omitted by the sender when serializing the leaf for inclusion in the asset tree, otherwise the tree wouldn't be predictable on the receiver side. This has a corresponding rule in the bip-tap-vm during the input mapping of the inclusion proof validation. - Add an additional output that sends a de minimis (in practice this MUST be above dust) amount to the top-level taproot public key computed earlier.
- Broadcast and sign the transaction, submitting the resulting Taproot Asset state transition proof to a Universe of choice, also known by the receiver.
- This new leaf MUST have a
- Post the resulting state transition proof to the specified Universe. The submitted proof must contain the optional auxiliary value of the full
split_commitment
the receiver requires to spend the asset.
Sending assets to an address is inherently a non-interactive process as there is
no active communication between the sender and recipient other than the exchange
of the address in the first place.
Because of the above mentioned requirement that an asset leaf created to send to
an address MUST have a split_commitment
, a special case exists if
there is no change going back to the sender (an asset output is fully consumed
by the transfer to an address): A special tombstone output with a value of
0 must be created for the split root asset (the root_asset
of the
split) that holds the transfer witness. The script_key
of the
split root asset output should be the well-known NUMS point (using the string
"taproot-assets" and the traditional "hash and increment" approach to generating
the point) to prove the output cannot be spent further. Such a tombstone output
can then be pruned from the tree when the UTXO is spent further.
More details about interactive and non-interactive sends and tombstone outputs
can be found in the bip-tap-psbt.
In order to spend (or simply confirm receipt) of the received asset, the receiver should:
- Re-derive the taproot public key script created above that sends to their specified Taproot Asset leaf.
- Wait for a transaction creating the output to be confirmed in the blockchain.
- In practice this may be via light client protocols such as BIP 157/158, or simply a full node with an address index, or import public key.
- For each previous outpoint referenced in the transaction:
- Look up the previous outpoint as a key into the chosen canonical Universe/Multiverse.
- If the key is found, verify the inclusion proof of the value (as described in bip-tap-proof-file), and extract the
split_commitment
inclusion proof for the output.
- If the key is found, verify the inclusion proof of the value (as described in bip-tap-proof-file), and extract the
- Look up the previous outpoint as a key into the chosen canonical Universe/Multiverse.
- Walk the Universe tree backwards in time to incrementally construct the full provenance proof needed to spend the asset.
Test vectors for Encoding an Address can be found here:
The test vectors are automatically generated by unit tests in the Taproot Assets GitHub repository.github.com/lightninglabs/taproot-assets/tree/main/address