-
Notifications
You must be signed in to change notification settings - Fork 22
Understanding Casts
A Bitcoin transaction consists of two sides: inputs and outputs.
An output contains a locking script which locks a number of satoshis to a puzzle. An input contains an unlocking script which provides the solution to a previous output's puzzle. These two sides of the puzzles, the locking and unlocking scripts, form a script template.
A Cast is a JavaScript class with an API for defining locking and unlocking scripts. A Cast is therefore a script template. It allows us to lock coins, and later unlock and spend coins, by calling a function and passing a few parameters.
TxForge ships with the following Casts, supporting some very common transaction templates:
- P2PKH - Pay to Public Key Hash (or pay to an address)
- P2PK - Pay to Public Key
- P2MS - Pay to Multi Sig
- P2RPH - Pay to R-Puzzle Hash
- OpReturn - Non-spendable data output
- Raw - Raw script pass-through
These Casts can be accessed:
import { casts } from 'txforge'
const { P2PKH, P2PK, P2MS, P2RPH, OpReturn } = casts
This is by far the most common transaction type in Bitcoin land. It locks satoshis to the hash of a public key (an address). The satoshis can be unlocked by signing with the corresponding private key.
import { forgeTx, getUTXO, casts } from 'txforge'
import nimble from '@runonbitcoin/nimble'
const { P2PKH } = casts
const privkey = nimble.PrivateKey.fromStr(wif)
const address = privkey.toAddress()
const tx1 = forgeTx({
inputs: [...],
outputs: [
P2PKH.lock(10000, { address })
]
})
const utxo = getUTXO(tx1, 0)
const tx2 = forgeTx({
inputs: [
P2PKH.unlock(utxo, { privkey })
],
outputs: [...]
})
P2PK
scripts are simpler and smaller than their P2PKH cousins, but seen far less frequently. It locks satoshis to a public key. The satoshis can be unlocked by signing with the corresponding private key.
import { forgeTx, getUTXO, casts } from 'txforge'
import nimble from '@runonbitcoin/nimble'
const { P2PK } = casts
const privkey = nimble.PrivateKey.fromStr(wif)
const pubkey = privkey.toPublicKey()
const tx1 = forgeTx({
inputs: [...],
outputs: [
P2PK.lock(10000, { pubkey })
]
})
const utxo = getUTXO(tx1, 0)
const tx2 = forgeTx({
inputs: [
P2PK.unlock(utxo, { privkey })
],
outputs: [...]
})
P2MS
scripts allow for satoshis to be locked to a threhold of a number of public keys. The satoshis can be unlocked by signing with the freshold of corresponding private keys. For example, if the coin is locked 3 public keys with a threshold of 2, then it requires two valid signatures to spend the coin.
import { forgeTx, getUTXO, casts } from 'txforge'
import nimble from '@runonbitcoin/nimble'
const { P2MS } = casts
const privkeys = [pk1, pk2, pk3]
const pubkeys = privkeys.map(pk => pk.toPublicKey())
const tx1 = forgeTx({
inputs: [...],
outputs: [
P2PK.lock(10000, { pubkeys, threshold: 2 })
]
})
const utxo = getUTXO(tx1, 0)
const tx2 = forgeTx({
inputs: [
P2PK.unlock(utxo, { privkeys.slice(0, 2) })
],
outputs: [...]
})
R Puzzles are a kind of hash puzzle. Unlike a traditional hash puzzle, R Puzzles take advantage of ECDSA mathmetics so that the secret knowledge never needs to be revealed on-chain.
P2RPH
scripts involve locking satoshis to the hash of an R
value. R
is part of an ECDSA signature that is the result of multiplying the ECDSA generator G
with a secret value K
. The satoshis can therefore be unlocked by signing with any key as long we we use the correct K
value.
The benefits of this script are two-fold. Our secret value K
is never revealed publicly, and we can in effect sign a valid transaction using any arbitrary key.
TxForge provides an extra/r-puzzle
export that contains helpers for generating the values K
and R
used in R-Puzzles. If a private key isn't provided to unlock the coin, then a random key is used to create the signature.
import { forgeTx, getUTXO, casts } from 'txforge'
import { generateK, calculateR } from 'txforge/extra/r-puzzle'
import nimble from '@runonbitcoin/nimble'
const { P2RPH } = casts
const k = generateK()
const r = calculateR(k)
const tx1 = forgeTx({
inputs: [...],
outputs: [
P2RPH.lock(10000, { r })
]
})
const utxo = getUTXO(tx1, 0)
const privkey = nimble.PrivateKey.fromStr(wif)
const tx2 = forgeTx({
inputs: [
P2RPH.unlock(utxo, { k, privkey })
],
outputs: [...]
})
Sometimes it is useful to attach arbitrary data to a transaction. This is commonly achieved by creating a non-spendable output that always returns false containing the attached data.
As OpReturn
locking scripts are always unspendable, we generally wont want to lock any satoshis to this output. The data
parameter can be a string or byte vector, or an array of valid values.
import { forgeTx, casts } from 'txforge'
import nimble from '@runonbitcoin/nimble'
const { OpReturn } = casts
const tx = forgeTx({
inputs: [...],
outputs: [
OpReturn.lock(0, { data: 'hello world' }),
OpReturn.lock(0, { data: ['hello', 'world'] })
]
})
Sometimes you may want to use TxForge with existing scripts. We got you covered - the Raw
Cast is for exactly this use case. Simply pass in your script as a parameter.
import { forgeTx, casts } from 'txforge'
const { Raw } = casts
const script = '76a914a58621fd80c3abcf4f81d343f6a78dc891082d6688ac'
const tx = forgeTx({
inputs: [...],
outputs: [
Raw.lock(10000, { script })
]
})