How to Write Custom Bitcoin Scripts in Bitauth IDE

Bitauth IDE is an integrated development environment for bitcoin authentication. This guide explains features of the IDE and language.

Bitcoin Script is the language used by Bitcoin to validate transactions. Bitcoin “addresses” are actually locking scripts defined in this language, and in order to spend funds, every transaction must have a matching unlocking script, which provides the signatures and other data required to “unlock” the funds.

To learn more about Bitcoin Script, check out Bitcoin Script for Beginners.


Bitauth IDE is an integrated development environment for bitcoin authentication. This guide will explain features of the IDE and templating language.

This guide is also built-in to the IDE itself, so if you’re on a desktop, you can also read it there.

Bitauth Templates

When you work in Bitauth IDE, you’re working on a Bitauth Template. It’s a JSON file which fully describes the authentication scheme for a bitcoin wallet. Compatible wallet software can import your template and generate a fully-functional wallet, even for complex, multi-party schemes. The IDE lets you write, test, and export Bitauth templates.

Bitauth templates include two primary concepts:

  • Entities — the individuals and/or devices participating in the wallet.
  • Scripts — the code used by wallet software to create addresses and transactions.

Entities

A Bitauth template defines a set of entities which will use the template. Each entity can be assigned one or more variables for which they are responsible. There are currently 6 variable types: Key, HDKey, WalletData, AddressData, CurrentBlockHeight, and CurrentBlockTime.

When a wallet is created, each entity shares the public elements of their variables. Values are validated to prevent man-in-the-middle attacks, and then wallet addresses are generated.

You can find the Entities for the current Bitauth Template in the menu to the left. Here you can see the settings and variables for “Signer 1”, one of two co-owners in this 2-of-2 wallet with a time-delayed fallback.

Scripts

Bitauth templates define a set of scripts used by the entities. There are 4 types of scripts:

  • Locking Scripts — scripts from which wallet addresses are generated.
  • Unlocking Scripts — scripts which enable wallet software to spend from the wallet.
  • Isolated Scripts — scripts used as macros or bytecode templates.
  • Isolated Script Tests — a pair of scripts (Setup and Check) used to verify the functionality of an isolated script.

Bitauth Templating Language (BTL)

Bitauth template scripts are written in Bitauth Templating Language (BTL). The language is very low-level — any bitcoin virtual machine bytecode can be represented in BTL.

Opcodes

Opcode identifiers in BTL are prefixed with OP_. During compilation, opcode identifiers are replaced with their bytecode equivalents. E.g OP_0 OP_1 OP_ADD will compile to the bytecode 005193 (hex-encoded).

All opcodes are also autocompleted within the IDE. To read a description of a given opcode, hover over it in the editor. You can also find resources describing bitcoin opcodes online.

The IDE provides autocompletion and inline documentation for each opcode. Type “OP” to scroll through and explore the list.

Literal Data Types

BTL supports 3 literal data types:

  • Hex literals — hex-encoded data, prefixed with 0x, e.g. 0xc0de.
  • UTF8 literals — UTF8-encoded data, surrounded by single quotes (') or double quotes ("), e.g. 'this is a string' or "UTF8 👍".
  • BigInt literals — integers, e.g. 1234. (Bitauth IDE supports arbitrary integer sizes, but numbers which overflow 64 bits are considered non-standard and may not be supported in the future.)
Try selecting “Scratch Pad” from the welcome screen to experiment with literal types.

Push Statements

Push statements are surrounded by < and >, and generate the opcode to push their compiled contents to the stack.

For example <"abc"> will generate the bytecode to push "abc" (616263) to the stack: 03616263 (disassembled: OP_PUSHBYTES_3 0x616263). Pushes are automatically minimized: e.g. <1> compiles to 51 (disassembled: OP_1), and <OP_0> (equivalent to <0x00>) compiles to 0100 (disassembled: OP_PUSHBYTES_1 0x00).

Any valid BTL can be contained in a push statement (including further push statements), so code like <<<<1>>>> is valid. (Result: 03020151)

Hover over any segment of code in the editor to see its compiled bytecode form (which is being evaluated on the right). In this example, we push a push (2) of a push (3) of a push (4) of the number 1. The first push is optimized to “OP_1” (0x51), the second push prefixes the result with an “OP_PUSHBYTE_1” (0x01), and so on. You can see that the stack reflects the final push of "0x020151" (a valid Script Number equal to 5308674).

Including Variables & Scripts

Every Script and variable has a unique ID within the template. Both can be included by referencing the unique ID. E.g. an AddressData variable with an ID of nonce can be pushed to the stack with <nonce>.

When referenced, variables and scripts are included directly as bytecode. This makes it possible to provide segments of bytecode in variables and to use isolated scripts as macros. E.g. <my_number> pad_value might push the variable my_number and then insert the pad_value script, which might be defined as <8> OP_NUM2BIN, padding my_number to 8 bytes.

The “pad_nonce” script being used as a macro. In the top window, the functionality of “Pad Nonce” is tested by “Test Padding”, and the test is passing. Below, you can see “pad_nonce” being used in another script.

Variable Types

Each variable has a type which specifies its role in a template. There are currently 6 variable types:

  • AddressData– Address Data is the most low-level variable type. It must be collected and stored each time a script is generated (usually, a locking script). Address Data can include any type of data, and can be used in any way. For more persistent data, use WalletData.
  • CurrentBlockHeight– The Current Block Height type provides the current block height as a Script Number at the time of compilation. This is useful when computing a height forOP_CHECKLOCKTIMEVERIFY/OP_CHECKSEQUENCEVERIFY which is relative to the height at the moment a script is created (usually, a locking script).
  • CurrentBlockTime– The Current Block Time type provides the current block time (at the time of compilation) as a Script Number. This is useful when computing a time forOP_CHECKLOCKTIMEVERIFY/OP_CHECKSEQUENCEVERIFY which is relative to the current time at the moment a script is created (usually, a locking script).
  • HDKey– The HD Key (Hierarchical-Deterministic Key) type automatically manages key generation and mapping in a standard way. For greater control, use a Key. (NOTE: HDKey is not yet supported by Bitauth IDE.)
  • Key– The Key type provides fine-grained control over key generation and mapping. Most templates should instead use HDKey.
  • WalletData– The Wallet Data type provides a static piece of data – collected once and stored at the time of wallet creation. Wallet Data is persisted for the life of the wallet, rather than changing from locking script to locking script. For address-specific data, use AddressData.
The interface for defining a variable. Here you can see the definition of delay_seconds in the built-in “2-of-2 with Business Continuity” template.

Variable Operations

Some variable types provide operations which are accessed with a period (.), e.g. the public key of the owner Key can be pushed to the stack with <owner.public_key>.

Several operations are available to Key and HDKey variables:

  • public_key– include the public key.
  • signature.[signing_serialization_type]– create an ECDSA signature using the key and the selected signing serialization algorithm.
  • schnorr_signature.[signing_serialization_type]– create a schnorr signature using the key and the selected signing serialization algorithm.
  • data_signature.[SCRIPT_ID]– create a data signature using the key by signing the compiled output of SCRIPT_ID.
  • schnorr_data_signature.[SCRIPT_ID]– create a schnorr data signature using the key by signing the compiled output of SCRIPT_ID.
All scripts, variables, and operations can be autocompleted, and documentation is provided inline.

Signatures

Signatures (signature and schnorr_signature) are generated by serializing elements of the signed transaction in a standard way, hashing the serialization, and signing the message hash.

There are 6 signing serialization algorithms:

  • all_outputs– the recommended (and most commonly used) signing serialization algorithm. This signs each element of the transaction using the private key, preventing an attacker from being able to reuse the signature on a modified transaction. (A.K.A. "SIGHASH_ALL")
  • all_outputs_single_input– a modification to the "all_outputs" signing serialization algorithm which does not cover inputs other than the one being spent. (A.K.A. "SIGHASH_ALL" with "ANYONE_CAN_PAY")
  • corresponding_output– a signing serialization algorithm which only covers the output with the same index value as the input being spent. Warning: this can cause vulnerabilities by allowing the transaction to be modified in certain ways after being signed. (A.K.A. "SIGHASH_SINGLE")
  • corresponding_output_single_input– a modification to the "corresponding_output" signing serialization algorithm which does not cover inputs other than the one being spent. (A.K.A. "SIGHASH_SINGLE" with "ANYONE_CAN_PAY")
  • no_outputs– a signing serialization algorithm which only covers other inputs. Warning: this allows anyone to modify the outputs after being signed. (A.K.A. "SIGHASH_NONE")
  • no_outputs_single_input– a modification to the "no_outputs" signing serialization algorithm which does not cover inputs other than the one being spent. (A.K.A. "SIGHASH_NONE" with "ANYONE_CAN_PAY")

Most authentication schemes should use the all_outputs setting, e.g. <owner.signature.all_outputs>. This prevents an attacker from being able to reuse the signature on a different transaction (which the key holder did not intend to authorize).

For unique circumstances, the other algorithms can also be specified — you can find resources online which describe some of these scenarios and their security implications.

To display debugging information, Bitauth IDE transparently integrates scripts into a simple transaction and evaluates it in the bitcoin-ts virtual machine implementation.

The schnorr_signature operation being used with the all_outputs signing serialization algorithm.

Data Signatures

For data signatures (data_signature and schnorr_data_signature), the message to hash and sign is provided as a script, e.g. <owner.data_signature.message> will hash the compiled bytecode representation of the message isolated script, signing the hash using the owner Key.

Data signatures cover other scripts, so it’s easy to create a script for a preimage and use the resulting bytecode in both pushes and signing operations.

Evaluations

Evaluations are segments of code surrounded by $( and) which use the bitcoin virtual machine itself to assist in generating bytecode. The contents of an evaluation are compiled and evaluated, and the top element on the resulting stack is then inserted as bytecode. E.g. $(<1> <2> OP_ADD) "abc" produces the bytecode 03616263 (disassembled: OP_PUSHBYTES_3 0x616263).

This is surprisingly useful — often the procedure to create a desired bytecode sequence is similar to the procedure later used to validate it. For example, a P2SH locking script is generated using this BTL:

OP_HASH160 <$(<redeem_script> OP_HASH160)> OP_EQUAL

First, in the evaluation, the compiled bytecode of redeem_script is pushed to the stack and hashed. Then the final locking script can be generated by inserting the bytecode for OP_HASH160, followed by a push of the generated redeem script hash, followed by the bytecode for OP_EQUAL.

Evaluations also offer a way to make variables more powerful — here we add delay_seconds to the block time (at wallet creation) to arrive at the value used in this locking script’s OP_CHECKLOCKTIMEVERIFY operation.

Getting Started

The easiest way to get started working with Bitauth IDE is to review the example templates. You’ll find examples of both common wallet types and of complex, multi-entity authentication schemes.

Thanks for reading!

This guide is still under development. If you have questions or ideas for improvement, please open an issue on GitHub or message me on twitter.