Raw Transactions

Balzac generates real Bitcoin transactions, both for the mainnet and the testnet. This page explains how to publish transactions and use real transactions in Balzac.

Publish a transaction

Balzac transactions are compiled in a raw format, i.e. transactions are serialized to a string of bytes encoded in hexadecimal format.

You can use the Bitcoin client, or trust online services, to publish raw transactions. For example, the explorer BlockCypher allows to publish transaction on the testnet through the page https://live.blockcypher.com/btc-testnet/pushtx/. Since private keys are never part of a transaction, it’s perfectly fine to use external services to broadcast a transaction. At worst, the transaction may be dropped.

_images/push-tx.png

Real transactions as Input

Balzac supports real Bitcoin transactions, expressed in hexadecimal format. The prefix tx:, followed by the hexadecimal string of a serialized transaction, permits to create a constant of type transaction that can be used as input.

const Traw = tx:020000000001028a4415d1954ed05164e294...

transaction T1 {
    input = Traw : sig(k)
    output = ...
}

Balzac still checks that the witness correctly spends the input transaction. However, there is a limitation that depends on Bitcoin output scripts format and the following section explains how Balzac compiles the output scripts and how to use the two kinds of transaction that it may generate.

Output scripts format

Balzac provides an high-level language to express output scripts. Bitcoin supports some types of output scripts format. Some of them are:

  • P2PK (Pay to Public Key), which encode the public key in the output. This format is deprecated by P2PKH.
  • P2PKH (Pay to Public Key Hash) which encode the hash of the public key in the output, and the public key must be provided as input (alongside a signature).
  • P2SH (Pay to Script Hash) which encodes the hash of the script in the output and requires the script to be provided as input (alongside its actual parameters).

Balzac adopts P2PKH and P2SH to encode output scripts.

P2PKH

Balzac compiles fun(x) . versig(k;x) as P2PKH, i.e. Pay to Public Key Hash.

The public key is hashed and stored in the output script. Anyone who wants to redeem this output must provide a valid signature and the public key that corresponds with this hash.

This pattern is so common and widespread that the notion of Bitcoin address arose from it. In Bitcoin, an address is the hash of the public key encoded in Base 58 (plus other information like the network type and a checksum). See Address - Bitcoin Wiki for further details.

As an example, consider the transaction T defined below.

const kpub = pubkey:020ffce813c6e42b76e56aaa794a001f9f27e09d9dbe5a5a83d9712f9ba4fdbe8b

transaction T {
    input = _
    output = 10 BTC : fun(x) . versig(kpub;x)
}

The public key kpub is hashed and stored in the output script of T. The Bitcoin address associated to kpub is mkYSk8yaNfurMmo5aPsPPDym6hjz6VM2un and can be obtained in Balzac typing kpub.toAddress.

A transaction T1 that spends T is shown in the following example.

const k = key:cRLAzgrJJQA61pcUkUeasn2FDXLEuWxfXMY4YeGs3cXUCf7vj4bU

transaction T1 {
    input = T : sig(k)
    output = 10 BTC : fun(x) . ...
}

The witness of T1 provides a valid signature for kpub. However, remember that kpub is not stored in the output script of T, but only its hash. In theory, the public key should be provided alongside with the signature sig(k), so that it can be compared with the hash in the output script before the validation. In practice, Balzac recognizes P2PKH output scripts and provides the public key for us.

const k = key:cRLAzgrJJQA61pcUkUeasn2FDXLEuWxfXMY4YeGs3cXUCf7vj4bU

transaction T1 {
    input = T : sig(k) kpub     // Error: invalid number of witnesses
    output = 10 BTC : fun(x) . ...
}

Serialized P2PKH transactions

Transactions that encode P2PKH outputs can be smoothly used in Balzac.

const kpub = pubkey:020ffce813c6e42b76e56aaa794a001f9f27e09d9dbe5a5a83d9712f9ba4fdbe8b

transaction T {
    input = _
    output = 10 BTC : fun(x) . versig(kpub;x)
}

const Traw = tx:02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff02012affffffff0100ca9a3b0000000017a91413e090734f942aba5c7cdaf98caaa7ce19cadc368700000000

eval T == Traw  // true

In this example, the transaction Traw is obtained by the serialization of T. As you can notice below, T1 spends Traw and there is no difference between redeeming T or Traw.

const k = key:cRLAzgrJJQA61pcUkUeasn2FDXLEuWxfXMY4YeGs3cXUCf7vj4bU

transaction T1 {
    input = Traw : sig(k)
    output = 10 BTC : fun(x) . ...
}

P2SH

Balzac compiles all the output scripts that are different from fun(x) . versig(k;x) as P2SH, i.e. Pay to Script Hash.

The script is serialized, then hashed, and finally stored in the output script. Anyone who wants to redeem this output must provide the actual parameters for the script and the script itself, serialized. If the script hash matches the hash in the output script and its execution evaluates to true, the output is redeemed.

Consider the following example.

const kpub = pubkey:020ffce813c6e42b76e56aaa794a001f9f27e09d9dbe5a5a83d9712f9ba4fdbe8b

transaction T {
    input = _
    output = 10 BTC : fun(x, secret:string) .
        versig(kpub;x) && sha1(secret) == hash:aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
}

The output script takes two inputs, a signature x and a string secret, and evaluates to true if x is valid signature for kpub and the sha1 of secret is equal to the embedded hash.

A transaction T1 that spends T is shown in the following example

const k = key:cRLAzgrJJQA61pcUkUeasn2FDXLEuWxfXMY4YeGs3cXUCf7vj4bU

transaction T1 {
    input = T : sig(k) "hello"
    output = 10 BTC : fun(x) . ...
}

Remember that the output script of T is not stored when the transaction is serialized. So, alongside the actual parameters sig(k) "hello", the transaction T1 should provide the output script. However, in Balzac this is not required because it is done automatically.

Serialized P2SH transactions

Problems arise when the output script of a serialized transaction is a P2SH. In fact, a serialized P2SH only contains the hash of the script.

Consider the following example.

const kpub = pubkey:020ffce813c6e42b76e56aaa794a001f9f27e09d9dbe5a5a83d9712f9ba4fdbe8b

transaction T {
    input = _
    output = 10 BTC : fun(x, secret:string) .
        versig(kpub;x) && sha1(secret) == hash:aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
}

const Traw = tx:02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff02012affffffff0100ca9a3b0000000017a9149a43eb9f4ae32ff9234dc1ba92ebfeffc83c18e78700000000

eval T == Traw      // true

In this example, the transaction Traw is obtained by the serialization of T. However, the following example will not work.

const k = key:cRLAzgrJJQA61pcUkUeasn2FDXLEuWxfXMY4YeGs3cXUCf7vj4bU

transaction T1 {
    input = Traw : sig(k) "hello"   // Error
    output = 10 BTC : fun(x) . ...
}

When using a raw transaction as input, the output script of the transaction must be provided beside the actual parameters. There is no chance that Balzac will guess what is the output script just looking at its hash.

The script, called redeem script, is specified between square brackets [], after the witnesses. In the following example, T1 spends Traw providing the redeem script.

const k = key:cRLAzgrJJQA61pcUkUeasn2FDXLEuWxfXMY4YeGs3cXUCf7vj4bU

transaction T1 {
    input = Traw : sig(k) "hello" [fun(x, secret:string) . versig(kpub;x) && sha1(secret) == hash:aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d]
    output = 10 BTC : fun(x) . ...
}

If the script is not specified, Balzac complains that the redeem script is missing. Also, a wrong script will result in a wrong evaluation, and T1 does not redeem Traw.