Proof of Issuance - Blockstream’s Liquid Asset Registry

Simple Issuance Example

This example shows how to use Blockstream’s Liquid Asset Registry. The asset registry allows you to register an asset and prove ownership against a domain name.

The code below creates an asset, an associated token, and outputs three files:

  • A liquid-asset-proof-<asset-id> file that must be placed on the server of the registered domain over the entire lifecycle of the asset.

  • A register_asset_<asset-id>.sh file that, when run, will post the asset registration data to Blockstream’s Liquid Asset Registry.

  • A delete_asset_<asset-id>.sh file that, when run, will delete the asset from the registry.

The use of the output files will be explained later.

There are six fields that need setting within the script:

  • NAME The name of the asset as it will apear in the registry and applications that use asset registry data, such as the Blockstream Explorer. Length must be 5 to 255 ASCII characters.

  • TICKER The ticker you would like to assign to the asset. Length must be 3 to 5 characters. Valid characters are: a-z A-Z 0-9 . and -. If provided, ticker has to be unique when combined with the domain field.

  • DOMAIN The domain that will be used to verify the asset. Must be a valid domain name format and only supports (sub)domain names, for example: example.com or sub.example.com. Do not include the http/s or www prefixes. To verify you control the entity domain name, you’ll need to place a file on your webserver. Note that serving the file with https is required, except for .onion hidden services. The file should be made available at https://<domain>/.well-known/liquid-asset-proof-<asset-id>, with the following contents (See line 133 of the code example below for an example of file content generation): Authorize linking the domain name <domain> to the Liquid asset <asset-id>

  • ASSET_AMOUNT The amount to be issued. It is preferable to consider one satoshi unit as representing one asset. So to issue an amount of 100 of an asset, a value of 0.00000100 should be used. Please note that how this value is displayed within applications that use asset registry data is affected by the PRECISION field.

  • TOKEN_AMOUNT The amount of reissuance tokens to be created. It is preferable to consider one satoshi unit as representing one reissuance token, so a value of 0.00000001 will create 0.00000001 reissuance tokens and when viewed from Liquid Core it will also show as 0.00000001. When viewed from an app that uses satoshi sized units, such as the Blockstream Explorer it will show as 1. This field is not affected by the PRECISION field.

  • PRECISION The precision used to display the asset amount within applications that use Liquid Asset Registry data, such as the Blockstream Explorer. Represents the number of digits after the decimal point, i.e. 0 for non-divisible assets or 8 for BTC-like divisible assets. See the examples below.

Precision examples:

A PRECISION value of 0 for an ASSET_AMOUNT of 0.00000100 will create 0.00000100 when viewed from Liquid Core and 100 when viewed in an app that uses the asset registry data, which shifts the decimal 0 places left from the sats value.

A PRECISION value of 2 for an ASSET_AMOUNT of 0.00000100 will create 0.00000100 when viewed from Liquid Core and 1.00 when viewed in an app that uses the asset registry data, which shifts the decimal 2 places left from the sats value.

A PRECISION value of 8 for an ASSET_AMOUNT of 0.00000100 will create 0.00000100 when viewed from Liquid Core and 0.00000100 when viewed in an app that uses the asset registry data, which shifts the decimal 8 places left from the sats value. This equates to BTC units.

The default is 0 and the maximum value is 8.

Please note that precision is not taken into account when displaying reissuance token amounts. So if you issue your asset with 1 reissuance token it will always show as 100 000 000 (the amount 1.00 in sats) via Blockstream Explorer and other apps using the registry. In Elements it will show as 1.00000000.

  • PUBKEY holds the public key value that goes into the issuer_pubkey registration data field. The code example derives this from a new address generated by a private key in the node’s wallet.dat file. For safety, this file should be backed up after running the script. Any ECDSA secp256k1 public key could be used in its place, which may be of use when the issuing entity is not the same as the entity running the script. You should ensure that, whatever PUBKEY value you use to set the registration data’s issuer_pubkey field, it is recorded somewhere and that the corresponding private key is backed up for potential future use in authorizing amendments to the registry data. Note that the script sets the PUBKEY variable’s value by calling getaddressinfo with a new address generated by the node’s wallet (line 52). The value of PUBKEY is then used to set the issuer_pubkey registration data field (line 64). The issuer_pubkey data field is used by the Asset Registry to verify ownership when removing or updating registry data in the future.

The code below uses Liquid in live mode. The code can easily be adapted for test use, although test assets cannot be registered as the registry connects to the live Liquid network only.

Save the code below in a file named issue_and_prepare_register.sh and take a back up of the script before you run it as you may wish to refer to it in the future to track how you created the registry data.

  1#!/bin/bash
  2set -x
  3
  4shopt -s expand_aliases
  5
  6# ASSUMES elementsd IS ALREADY RUNNING
  7
  8######################################################
  9#                                                    #
 10#    SCRIPT CONFIG - PLEASE REVIEW BEFORE RUNNING    #
 11#                                                    #
 12######################################################
 13
 14# Amend the following:
 15NAME="your asset name here"
 16TICKER="your ticker"
 17# Do not use a domain prefix in the following:
 18DOMAIN="domain.here"
 19# Issue 100 assets using the satoshi unit, dependant on PRECISION when viewed from
 20# applications using Asset Registry data.
 21ASSET_AMOUNT=0.00000100
 22# Issue 1 reissuance token using the satoshi unit, unaffected by PRECISION.
 23TOKEN_AMOUNT=0.00000001
 24
 25# Amend the following if needed:
 26PRECISION=0
 27
 28# Don't change the following:
 29VERSION=0
 30
 31# Change the following to point to your elements-cli binary and liquid live data directory (default is .elements).
 32alias e1-cli="elements-cli -datadir=$HOME/.elements"
 33
 34##############################
 35#                            #
 36#    END OF SCRIPT CONFIG    #
 37#                            #
 38##############################
 39
 40# Exit on error
 41set -o errexit
 42
 43# We will be using the issueasset command and the contract_hash argument:
 44# issueasset <assetamount> <tokenamount> <blind> <contract_hash>
 45
 46NAME="your asset name here"
 47TICKER="ticker here"
 48DOMAIN="domain.here"
 49ASSET_AMOUNT=100
 50TOKEN_AMOUNT=1
 51PRECISION=0
 52
 53# Don't change the following:
 54VERSION=0
 55
 56# As we need to sign the deletion request message later we need
 57# a legacy address. If you prefer to generate a pubkey and sign
 58# outside of Elements you can use a regular address instead.
 59NEWADDR=$(e1-cli getnewaddress "" legacy)
 60
 61VALIDATEADDR=$(e1-cli getaddressinfo $NEWADDR)
 62
 63PUBKEY=$(echo $VALIDATEADDR | jq -r '.pubkey')
 64
 65ASSET_ADDR=$NEWADDR
 66
 67NEWADDR=$(e1-cli getnewaddress "" legacy)
 68
 69TOKEN_ADDR=$NEWADDR
 70
 71# Create the contract and calculate the contract hash
 72# The contract is formatted for use in the Blockstream Asset Registry:
 73
 74CONTRACT='{"entity":{"domain":"'$DOMAIN'"},"issuer_pubkey":"'$PUBKEY'","name":"'$NAME'","precision":'$PRECISION',"ticker":"'$TICKER'","version":'$VERSION'}'
 75
 76# We will hash using openssl, other options are available
 77CONTRACT_HASH=$(echo -n $CONTRACT | openssl dgst -sha256)
 78CONTRACT_HASH=$(echo ${CONTRACT_HASH#"(stdin)= "})
 79
 80# Reverse the hash
 81TEMP=$CONTRACT_HASH
 82LEN=${#TEMP}
 83until [ $LEN -eq "0" ]; do
 84    END=${TEMP:(-2)}
 85    CONTRACT_HASH_REV="$CONTRACT_HASH_REV$END"
 86    TEMP=${TEMP::-2}
 87    LEN=$((LEN-2))
 88done
 89
 90# Issue the asset and pass in the contract hash
 91IA=$(e1-cli issueasset $ASSET_AMOUNT $TOKEN_AMOUNT false $CONTRACT_HASH_REV)
 92
 93# Details of the issuance...
 94ASSET=$(echo $IA | jq -r '.asset')
 95TOKEN=$(echo $IA | jq -r '.token')
 96ISSUETX=$(echo $IA | jq -r '.txid')
 97
 98#####################################
 99#                                   #
100#    ASSET REGISTRY FILE OUTPUTS    #
101#                                   #
102#####################################
103
104# Output the proof file - you need to place this on your domain.
105echo "Authorize linking the domain name $DOMAIN to the Liquid asset $ASSET" > liquid-asset-proof-$ASSET
106
107# Create the bash script to run after you have placed the proof file on your domain
108# that will call the registry and request the asset is registered.
109echo "curl https://assets.blockstream.info/ --data-raw '{\"asset_id\":\"$ASSET\",\"contract\":$CONTRACT}'" > register_asset_$ASSET.sh
110
111# Create the bash script to delete the asset from the registry (if needed later)
112PRIV=$(e1-cli dumpprivkey $ASSET_ADDR)
113SIGNED=$(e1-cli signmessagewithprivkey $PRIV "remove $ASSET from registry")
114echo "curl -X DELETE https://assets.blockstream.info/$ASSET -H 'Content-Type: application/json' -d '{\"signature\":\"$SIGNED\"}'" > delete_asset_$ASSET.sh
115
116# Stop the daemon
117e1-cli stop
118sleep 10
119
120echo "Completed without error"

When you have saved the above to the file, edit the variables at the top and of the file and start elements-qt or elementsd using an argument of -server=1 to allow the Liquid client to communicate with it. Execute the script from the directory you created it in by opening a Terminal session and running:

bash issue_and_prepare_register.sh

In order to register the asset just created:

  • Wait a couple of minutes for the issuance transaction to confirm.

  • Place the liquid-asset-proof-<asset-id> file in a folder named .well-known in the root of your domain. This proof should be stored there over the entire lifecycle of the asset.

  • Run the register_asset_<asset-id>.sh script.

For example, if your domain was www.your-example-domain-here.com and the asset id generated was 123abc (it will of course be much longer) then the file generated would be named:

liquid-asset-proof-123abc

The domain variable in the code above would be set to:

your-example-domain-here.com

So the path used to check asset to domain registry would end up being:

www.your-example-domain-here.com/.well-known/liquid-asset-proof-123abc

Once that file is accessible you can then run the register_asset_<asset-id>.sh script and, when the required checks against the domain and issuance transaction have been made, the registration will be found on Blockstream’s Liquid Asset Registry.

You can remove the asset from the registry by running delete_asset_<asset-id>.sh.

Asset Registry Specifications

Contract JSON Fields

Required fields:

  • version: currently 0

  • issuer_pubkey: the hex-encoded public key of the issuer

  • name: 1-255 ASCII characters

  • entity: the online entity linked to this asset which currently only supports (sub)domain names in the form of a nested object with {"domain":"foobar.com"}

Optional fields:

  • ticker: 3-5 characters consisting of a-z, A-Z, 0-9, . and -. If provided, has to be unique within the entity (domain name) namespace.

  • precision: number of digits after the decimal point, i.e. 0 for non-divisible assets or 8 for BTC-like. defaults to 0.

Example:

{
  "version": 0,
  "issuer_pubkey": "037c7db0528e8b7b58e698ac104764f6852d74b5a7335bffcdad0ce799dd7742ec",
  "name": "Foo Coin",
  "ticker": "FOO",
  "entity": { "domain": "foo-coin.com" },
  "precision":8
}

Contract Hash

The contract hash is the (single) sha256 hash of the contract json document, canonicalized to have its keys sorted lexographically.

The canonicalization can be done with Perl like so:

$ perl -e 'use JSON::PP; my $js = JSON::PP->new; $js->canonical(1); print $js->encode($js->decode($ARGV[0]))' '{"version":0,"name":"FOO",...}'

Or with Python like so:

$ python -c 'import json,sys; sys.stdout.write(json.dumps(json.loads(sys.argv[1]), sort_keys=True, separators=(",",":")))' '{"version":0,"name":"FOO",...}'

Or with JavaScript using the json-stable-stringify library.

The resulting sha256 hash needs to be reversed to match the format expected by elementsd, similarly to the reverse encoding of txids and blockhashes as originally implemented for bitcoin by Satoshi. This can be done in a unix environment like so:

echo <hash> | fold -w2 | tac | tr -d "\n"

All together:

$ CONTRACT='{"version":0,"ticker":"FOO","name":"Foo Coin"}'
$ CONTRACT_HASH=$(python -c 'import json,sys; sys.stdout.write(json.dumps(json.loads(sys.argv[1]), sort_keys=True, separators=(",",":")))' "$CONTRACT" | sha256sum | head -c64 | fold -w2 | tac | tr -d "\n")
$ echo $CONTRACT_HASH

Domain Ownership Proof

To verify you control the entity domain name, you’ll need to make a file on your webserver available at https://<domain>/.well-known/liquid-asset-proof-<asset-id>, with the following contents:

Authorize linking the domain name <domain> to the Liquid asset <asset-id>

Note that serving the file with https is required, except for .onion hidden services.

Issuing & Registering Assets

Prepare your contract json and get your contract hash. You can verify their validity before issuing the asset using the validation endpoint:

$ curl https://assets.blockstream.info/contract/validate -H 'Content-Type: application/json' \
      -d '{"contract": <your-contract-json>, "contract_hash": "<your-contract-hash>"}'

If everything seems good, issue the asset using elementsd’s rawissueasset with your hash as the contract_hash parameter and take note of the resulting asset_id. Add the domain ownership proof (as described above) and, once the issuance transaction confirms, submit the asset to the registry:

$ curl https://assets.blockstream.info/ -H 'Content-Type: application/json' \
      -d '{"asset_id": "<asset-id>", "contract": <contract-json>}'

Deleting Asset Registry Data

Deleting an asset from the Blockstream Asset Registry only removes its metadata from the registry. It does not, and can not, delete the asset itself from the Liquid Blockchain.

To delete an asset from the Blockstream Asset Registry, you neeed to sign and send a message authorizing the deletion. The message must be signed using the private key for the address associated with the issuer_pubkey used in registration.

You can use Elements to sign the message. First, dump the private key for the address that was used to get the pubkey:

elements-cli dumpprivkey <address>

Use the returned private key to sign a message of the format remove <asset-id> from registry.

The call to elements to sign the message is:

elements-cli signmessagewithprivkey <private key> "remove <asset-id> from registry"

The result of this is a base64 encoded signature. Post the signed message/signature to the asset registry like so:

$  curl -X DELETE https://assets.blockstream.info/<asset-id> -H 'Content-Type: application/json' \
    -d '{"signature":"<base64-encoded-signature>"}'

You will receive an Asset deleted message in response.

The asset will immediately be removed from the Liquid Asset Registry data. It may take a short while before the data deletion is replicated in the Blockstream Explorer itself.