This specification describes a protocol for decentralized, distributed issuance of tokens on networks, such as Bitcoin Cash (BCH) and eCash (XEC), that implement the Simple Ledger Protocol (SLP). By using this protocol, individual users can issue ("mint") new value, from an already created token, into existance from inside their own non-custodial wallet.
In the traditional model, SLP tokens are brought into existence via GENESIS and MINT transactions that are effectuated by the original issuer of the token and whose value immediately is placed under the control of said issuer. From there, tokens are distributed to other users as standard SEND transactions. There are some exceptions to this model, such as the MIST mineable SLP token.
The Simple Ledger Protocol Self Mint Token operates on a model where all token issuance is distributed and decentralized. In the canonical implementation, the original creator of the token - the entity that performed the original GENESIS transaction - never takes possession of any token value whatsoever. Instead, each new value issuance (MINT) is accomplished via a special covenant-based transaction with the value assigned to the individual user ("transaction signer") creating, signing, and broadcasting the transaction. The covenant consists of several key spending constraints that allow the original creator of the token to have limited control over new token issuance, without ever transmitting token value.
Initial creation of the token is done via a standard SLP Token Type 1 GENESIS with the following required values:
<mint_baton_vout>
0x02<initial_token_mint_quantity>
0x0000000000000000
The value of tokens created during GENESIS must be 0.
The funding input at index 0 must spend a UTXO that originates from a P2PKH address associated with the public key used in the self mint covenants. In other words, the public key in the scriptSig of vin 0
can be used to construct the covenants from a template or reference code.
The output at index 1 is a stamp assigned to the P2SH address for the self mint postage covenant associated with the token. This output represents a value of 0 tokens created in GENESIS. This should be the first output ever sent to that address. In the case of the transaction standard provided in this specification, the stamp amount is 2300 satoshis.
With a GENESIS transaction constructed in this manner, a user can derive all needed information to construct a self mint transaction given only the data provided in the authorizaton code, as a query for the first transaction associated with the same address as the stamp to be spent in any given transaction will return the token GENESIS transaction.
INDEX | INPUT | OUTPUT |
---|---|---|
0 | funding input | OP_RETURN SLP GENESIS |
1 | (0 tokens) self mint stamp 2300 satoshis | |
2 | (mint baton) 546 satoshis | |
3 | change |
The mint baton must be assigned, in the GENESIS transaction to a Self Mint covenant Pay-To-Script-Hash address. The script itself may take several forms, but the standard implementation will have the following spending constraints:
Input at index 0 (vin 0)
stamphash
index
Output at index 0 (vout 0)
must be a NULL DATA output representing a SLP Token Type 1 MINT<mint_baton_vout>
0x02<additional_token_quantity>
vout 1
scriptPubKey
Pay-To-Public-Key-Hash (P2PKH) where public key hash is HASH160 of public key of transaction signer
vout 2
scriptPubKey
returns baton to same P2SH address as it is being spent from (mint baton address assigned in original GENESIS transaction)
All constraints are accomplished via an oracle-like authorization signature provided by the original creator of the token, whose public key is made part of the covenant script.
An unspent transaction output (UTXO) with sufficient native value (eg. BCH satoshis) required to make the transaction valid is used as the first input (vout 0
) of the MINT transaction. We refer to this UTXO as a self mint stamp. Such UTXOs are created, in standard units, and assigned to a covenant that shares all spending constraints with the Self Mint Baton Covenant and contains one additional constraint:
Input at index 0 (vin 0)
must be the current input being signed
The use of the Self Mint Baton and Self Mint Postage covenants means there are two distinct but correlated P2SH addresses, from which UTXOs are used in every MINT transaction. In the canonical application, all MINT transactions will use only two inputs, one UTXO from each address - a stamp and the baton.
Because a UTXO can only be spent once, and since the hash and index of the self mint stamp are part of the spending constraints, each authorization signature is only valid for one transaction.
INDEX | INPUT | OUTPUT |
---|---|---|
0 | self mint stamp | OP_RETURN SLP MINT |
1 | mint baton | (n tokens) 546 satoshis |
2 | (mint baton) 546 satoshis same address as vin 1 |
In order to perform a Self Mint transaction, the end user must receive certain information from the original creator, whose public key is present in the Self Mint covenants described above. This information is delivered as a serialization of
bytes8 additional_token_quantity
(big endian) - as it appears in the MINT OP_RETURNbytes36 stamp_outpoint
- Bitcoincash-BIP143 outpoint (bytes32 hash
+bytes4 vout
) of the stamp UTXO to be spent.bytes authorization_signature
- the signature that will be used along with OP_CHECKDATASIG to execute the covenants
The code is parsed upon receipt by the end user and the three elements are used independently in the construction of the Self Mint transaction.
All covenant spending constraints are secured by a signature. The private key used to create this signature corresponds to the public key placed into the baton and postage covenants. The message signed is a serialization of
bytes36 stamp_outpoint
tx_outputs
- the serialization of all output amounts (bytes8
little endian) paired up with their scriptPubKey (serialized as scripts inside CTxOuts). This is the data on which is performed a double SHA256 to create hashOutputs in Bitcoincash-BIP143 transaction digest. Cosists of the following outputs:vout 0
bytes51 scriptPubKey
MINT OP_RETURNbytes8 satoshis
- 0
vout 1
bytes26 scriptPubKey
- P2PKH with public key hash corresponding the the key being used to perform the final OP_CHECKSIG on both inputsbytes8 satoshis
- 546
vout 2
bytes24 scriptPubKey
- P2SH for Self Mint Baton Covenantbytes8 satoshis
- 546
While the covenant locking script may take many forms and can be extended with more spending constraints than are listed in this specification, the elements of the unlocking script (scriptSig), used to spend Self Mint covenant UTXOs, are standardized. This standardization allows maximum interoperability between wallets and reduces the number of different implementations required to support a robust ecosystem of Self Mint tokens.
The standard Self Mint scriptSig consists of the following elements (in the order processed):
p2sh_subscript
prevout_sequence
- serialization of all input outpoints (bytes32 hash
+bytes4 vout
)tx_outputs
- the serialization of all output amounts (bytes8 satoshis
little endian) paired up with their scriptPubKey (as described in Authorization Signature section)authorization_signature
- Authorization Signaturepreimage
- Bitcoincash-BIP143 transaction digestspender_public_key
spender_signature
The following is an example of operations followed by a Self Mint covenant script during the unlock procedure
- Standard P2SH process, leaving subscript to execute stack
- (Self Mint Postage Input Only) Check that this input is at index 0
- Check that
scriptPubKey
atvout 1
corresponds tospender_public_key
- Check that spending constraints correspond to message signed by
authorization_signature
- Check that double SHA256 of
prevout_sequence
corresponds tohashPrevouts
inpreimage
- Check that double SHA256 of
outputs_sequence
corresponds tohashOutputs
inpreimage
- Validate
preimage
againstspender_public_key
andspender_signature
using OP_CHECKDATASIG - OP_CHECKSIG using
spender_public_key
andspender_signature
Initial creation of the token is done via a standard SLP Token Type 2 GENESIS with the following required values:
<initial_token_mint_quantity>
0x0000000000000000
The value of tokens created during GENESIS must be 0.
While the public key scheme described in the previous section of this document may be used, in this section is provided a means of doing a form of key rotation by including a Merkle root in the script vault locking script and then providing a public key and Merkle proof - demonstrating that the provided public key exists in an accepted set of public keys - in the unlocking script at the time of minting. This allows an authorizing entity to effectively rotate keys at intervals and represents an ostensibly more secure implementation of the specification.
HASH256 (double SHA256 hash) is the algorithm used to construct the Merkle tree used in this specification.
The Merkle proof, as used in the unlocking script, is a serialization of Merkle branches, in the order that the hashing operations are performed. In order to ease in computation at the time of script validation, the order of the values to be hashed is specified in the proof data provided in the unlocking script. Thus, an individual branch is the serialization of:
bytes1 hashing_order
-0x00
indicates that the accumulated result hash precedes the current branch level hash.0x01
indicates that the current branch level hash precedes the accumulated result hash.bytes32 branch_level_hash
INDEX | INPUT | OUTPUT |
---|---|---|
0 | funding input | OP_RETURN SLP GENESIS |
1 | (0 tokens) self mint stamp 2300 satoshis | |
2 | change |
The mint_vault_scripthash field in the GENESIS OP_RETURN must be assigned to a Self Mint covenant. The script itself may take several forms, but the standard implementation will have the following spending constraints:
Input at index 0 (vin 0)
valid UTXO originating from the MINT vaulthash
index
Input at index 0 final OP_CHECKSIG
associated public key is defined by authorizer in authorization code message/signatureOutputs
must precisely match those defined in the authorization code
All constraints are accomplished via an oracle-like authorization signature provided by the original creator of the token, whose public key (via Merkle proof method is made part of the covenant script. Because a UTXO can only be spent once, and since the hash and index of the self mint vault UTXO are part of the spending constraints, each authorization signature is only valid for one transaction.
INDEX | INPUT | OUTPUT |
---|---|---|
0 | self mint vault UTXO | OP_RETURN SLP MINT |
1 | (n tokens) 546 satoshis | |
2 | (n tokens) 546 satoshis | |
3 | (n tokens) 546 satoshis |
In order to perform a Self Mint transaction, the end user must receive certain information from the original creator, whose public key (or Merkle root) is present in the Self Mint covenant described above. This information is delivered as a serialization of
bytes32 authorizer_public_key
bytes merkle_proof
- the Merkle proof` that validates the provided public key against the Merkle root in the locking scriptbytes1 authorization_signature_length
bytes authorization_signature
- the signature that will be used along with OP_CHECKDATASIG to execute the covenantsbytes authorization_message
- the message signed byauthorization_signature
The code is parsed upon receipt by the end user and the elements are used in the construction of the Self Mint transaction.
All covenant spending constraints are secured by a signature. The private key used to create this signature corresponds to the public key provided in the authorization code. The message signed is a serialization of
bytes6 mint_id
- a required field used to identify and disambiguate individual authorization codes, as many codes may be issued that specify the exact same output databytes20 minter_pubkeyhash
- public key hash (HASH160) corresponding the the key being used to perform the final OP_CHECKSIG on the input at index 0. This restricts the authorization code to only be valid when used by an entity in control of a specific private keybytes36 mint_vault_UTXO_outpoint
tx_outputs
- the serialization of all output amounts (bytes8
little endian) paired up with their scriptPubKey (serialized as scripts inside CTxOuts). This is the data on which is performed a double SHA256 to create hashOutputs in Bitcoincash-BIP143 transaction digest. An example with 3 MINT outputs would consist of the following outputs:vout 0
bytes65 scriptPubKey
MINT OP_RETURN specifying 3 output amountsbytes8 satoshis
- 0
vout 1
bytes26 scriptPubKey
- Token output 1bytes8 satoshis
- 546
vout 2
bytes26 scriptPubKey
- Token output 2bytes8 satoshis
- 546
vout 3
bytes26 scriptPubKey
- Token output 3bytes8 satoshis
- 546
While the covenant locking script may take many forms and can be extended with more spending constraints than are listed in this specification, the elements of the unlocking script (scriptSig), used to spend Self Mint covenant UTXOs, are standardized. This standardization allows maximum interoperability between wallets and reduces the number of different implementations required to support a robust ecosystem of Self Mint tokens.
The standard Self Mint scriptSig consists of the following elements (in the order processed):
p2sh_subscript
merkle_data
- serialization of: *authorizer_public_key
*merkle_proof
authorization_data
- serialization of: *authorization_signature_length
*authorization_signature
*authorization_message
preimage
- Bitcoincash-BIP143 transaction digestspender_public_key
spender_signature
The following is an example of operations followed by a Self Mint covenant script during the unlock procedure
- Standard P2SH process, leaving subscript to execute stack
- Perform Merkle proof and verify that authorizer public key provided is valid
- Validate
authorization_message
andauthorization_signature
using OP_CHECKDATASIG - Check that spending constraints correspond to message signed by
authorization_signature
using preimage for transaction introspection - Validate
preimage
againstspender_public_key
andspender_signature
using OP_CHECKDATASIG - OP_CHECKSIG using
spender_public_key
andspender_signature
CashTab (wallet.badger.cash fork) - ReactJS
None currently
Reference code for creating covenant scripts using bcash (Badger.cash fork) JavaScript library
const buildOutScript = (authPubKey, checkIsFirstInput = false) => {
const script = new bcash.Script()
.pushSym('2dup')
.pushInt(36)
.pushSym('split')
.pushSym('drop');
if (checkIsFirstInput) {
script.pushSym('dup')
script.pushInt(6)
script.pushSym('pick')
script.pushInt(104)
script.pushSym('split')
script.pushSym('drop')
script.pushInt(68)
script.pushSym('split')
script.pushSym('nip')
script.pushSym('equalverify');
}
script.pushSym('swap')
.pushSym('dup')
.pushInt(78)
.pushSym('split')
.pushSym('nip')
.pushInt(20)
.pushSym('split')
.pushSym('drop')
.pushInt(7)
.pushSym('pick')
.pushSym('hash160')
.pushSym('equalverify')
.pushInt(132)
.pushSym('split')
.pushSym('drop')
.pushSym('cat')
.pushInt(3)
.pushSym('roll')
.pushSym('swap')
.pushData(authPubKey)
.pushSym('checkdatasigverify')
.pushInt(2)
.pushSym('roll')
.pushSym('dup')
.pushSym('size')
.pushInt(40)
.pushSym('sub')
.pushSym('split')
.pushSym('swap')
.pushInt(4)
.pushSym('split')
.pushSym('nip')
.pushInt(32)
.pushSym('split')
.pushSym('drop')
.pushInt(3)
.pushSym('roll')
.pushSym('hash256')
.pushSym('equalverify')
.pushInt(32)
.pushSym('split')
.pushSym('drop')
.pushSym('rot')
.pushSym('hash256')
.pushSym('equalverify')
.pushSym('sha256')
.pushSym('3dup')
.pushSym('rot')
.pushSym('size')
.pushSym('1sub')
.pushSym('split')
.pushSym('drop')
.pushSym('swap')
.pushSym('rot')
.pushSym('checkdatasigverify')
.pushSym('drop')
.pushSym('checksig')
.compile();
return script;
}
const type2Outscript = (merkleRootBuf) => {
const script = new Script()
.pushInt(33)
.pushSym('split')
.pushSym('over')
.pushSym('hash256')
.pushSym('swap');
// Iterate through merkle proof
for (let i = 0; i < 6; i++) {
script.pushInt(1)
.pushSym('split')
.pushInt(32)
.pushSym('split')
.pushSym('toaltstack')
.pushSym('swap')
.pushSym('bin2num')
.pushSym('if')
.pushSym('swap')
.pushSym('endif')
.pushSym('cat')
.pushSym('hash256')
.pushSym('fromaltstack');
}
script.pushInt(1)
.pushSym('split')
.pushSym('swap')
.pushSym('bin2num')
.pushSym('if')
.pushSym('swap')
.pushSym('endif')
.pushSym('cat')
.pushSym('hash256')
.pushData(merkleRootBuf)
.pushSym('equalverify')
.pushSym('swap')
.pushInt(1)
.pushSym('split')
.pushSym('swap')
.pushSym('bin2num')
.pushSym('split')
.pushSym('dup')
.pushSym('toaltstack')
.pushSym('rot')
.pushSym('checkdatasigverify')
.pushSym('fromaltstack')
.pushInt(26)
.pushSym('split')
.pushSym('swap')
.pushInt(6)
.pushSym('split')
.pushInt(4)
.pushSym('pick')
.pushSym('hash160')
.pushSym('equalverify')
.pushSym('drop')
.pushInt(36)
.pushSym('split')
.pushSym('hash256')
.pushInt(2)
.pushSym('pick')
.pushSym('size')
.pushInt(40)
.pushSym('sub')
.pushSym('split')
.pushInt(32)
.pushSym('split')
.pushSym('drop')
.pushSym('rot')
.pushSym('equalverify')
.pushInt(68)
.pushSym('split')
.pushSym('nip')
.pushInt(36)
.pushSym('split')
.pushSym('drop')
.pushSym('equalverify')
.pushSym('sha256')
.pushSym('3dup')
.pushSym('rot')
.pushSym('size')
.pushSym('1sub')
.pushSym('split')
.pushSym('drop')
.pushSym('swap')
.pushSym('rot')
.pushSym('checkdatasigverify')
.pushSym('drop')
.pushSym('checksig')
.compile();
return script;
}