Skip to content

Instantly share code, notes, and snippets.

@bodily11
Created July 21, 2023 12:25
Show Gist options
  • Save bodily11/58773d64b9d0e3e8a18a81ae665fa44e to your computer and use it in GitHub Desktop.
Save bodily11/58773d64b9d0e3e8a18a81ae665fa44e to your computer and use it in GitHub Desktop.
Script to inscribe using P2WSH
import requests, secrets, os, binascii, base58
from copy import deepcopy
from hashlib import sha256
from bitcoin.core import COIN, b2x, b2lx, lx, Hash160, CMutableTransaction, CMutableTxOut, CMutableTxIn, CTxInWitness, CTxWitness, COutPoint, CTransaction
from bitcoin.core.script import CScript, OP_CHECKSIG, SIGHASH_ALL, CScriptWitness, OP_IF, OP_ENDIF, OP_1, OP_0, OP_FALSE, SignatureHash, OP_DUP, OP_HASH160, OP_EQUALVERIFY, SIGVERSION_WITNESS_V0
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from bitcoin.wallet import CBitcoinSecret, CBitcoinAddress, P2WSHBitcoinAddress, P2PKHBitcoinAddress
from bitcoin import SelectParams
def generate_private_key():
# Generate a new private key
private_key_bytes = secrets.token_bytes(32)
# 0x80 byte in front for mainnet addresses
# 0xef for testnet addresses
# 0x01 at the end for compressed
extended_key = b"\x80" + private_key_bytes + b'\x01'
# Perform SHA-256 hash twice
hashed_extended_key = sha256(sha256(extended_key).digest()).digest()
# Take the first 4 bytes of this hash and use them as a checksum
checksum = hashed_extended_key[:4]
# Append checksum to the extended key
final_key = extended_key + checksum
# Base58 encode the key
WIF_private_key = base58.b58encode(final_key).decode('utf-8')
# Check the WIF key is valid
seckey = CBitcoinSecret(WIF_private_key)
return WIF_private_key
def create_bitcoin_address(private_key):
seckey = CBitcoinSecret(private_key)
# Compute public key
pubkey = seckey.pub
# Compute public key hash
pubkey_hash = Hash160(pubkey)
# Generate Bitcoin address from public key hash
address = P2PKHBitcoinAddress.from_pubkey(pubkey)
return str(address)
def create_commit_transaction(
private_key, # as string
input_txn, # transaction id
input_index, # output index
input_balance, # in sats
content_type, # mime type for your inscription content as string
inscription_bytes, # actual inscription content in bytes
original_address, # where to return extra Bitcoin, must be P2PKH for now
commit_fee, # how much to pay in total sats
reveal_fee # how much to pay in total sats
):
SelectParams('mainnet')
seckey = CBitcoinSecret(private_key)
# lx() takes *little-endian* hex and converts it to bytes; in Bitcoin
# transaction hashes are shown little-endian rather than the usual big-endian.
txid = lx(input_txn)
vout = int(input_index)
# Create the txin structure, which includes the outpoint.
# The scriptSig defaults to being empty.
txin = CMutableTxIn(COutPoint(txid, vout))
# We also need the scriptPubKey of the output we're spending because
# SignatureHash() replaces the transaction scriptSig's with it.
# Here we'll create that scriptPubKey from scratch using the pubkey that
# corresponds to the secret key we generated above.
txin_scriptPubKey = CScript([OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG])
# Your modified redeemScript
# redeemScript = CScript([1, seckey.pub, 1, OP_CHECKMULTISIG])
# redeemScript = CScript([seckey.pub, OP_CHECKSIG])
# Create the redeem script
redeemScript = CScript([seckey.pub, OP_CHECKSIG, OP_FALSE, OP_IF, data1, b'\x01', data2, OP_0, data3, OP_ENDIF])
# save the redeemScript to use in the reveal
redeemScriptCopy = deepcopy(redeemScript)
# Create the P2WSH scriptPubKey
p2wsh_scriptPubKey = CScript([OP_0, sha256(redeemScript).digest()])
# Compute the P2WSH address from the scriptPubKey
p2wsh_address = P2WSHBitcoinAddress.from_scriptPubKey(p2wsh_scriptPubKey)
original_address = CBitcoinAddress(original_address)
postage = 333 # Amount to use as postage for inscriptiong (in satoshis)
reveal_fee = int(reveal_fee) # Fee for the reveal transaction (in satoshis)
commit_fee = int(commit_fee) # Fee for the commit transaction (in satoshis)
leftover_amount = input_balance - postage - commit_fee - reveal_fee
# Create each output
txout1 = CMutableTxOut(postage + reveal_fee, p2wsh_address.to_scriptPubKey())
txout2 = CMutableTxOut(leftover_amount, original_address.to_scriptPubKey())
# Create the unsigned transaction
tx = CMutableTransaction([txin], [txout1, txout2])
# Calculate the signature hash for that transaction.
sighash = SignatureHash(txin_scriptPubKey, tx, 0, SIGHASH_ALL)
# Now sign it. We have to append the type of signature we want to the end, in
# this case the usual SIGHASH_ALL.
sig = seckey.sign(sighash) + bytes([SIGHASH_ALL])
# Set the scriptSig of our transaction input appropriately.
txin.scriptSig = CScript([sig, seckey.pub])
# Verify the signature worked. This calls EvalScript() and actually executes
# the opcodes in the scripts to see if everything worked out.
# If it doesn't an exception will be raised.
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
# Serialize the transaction for broadcasting
raw_transaction = b2x(tx.serialize())
txn_cost_in_sats = tx.calc_weight() / 4 * 50
txn_id = b2lx(tx.GetTxid())
return {
'raw_txn':raw_transaction,
'txn_cost':txn_cost_in_sats,
'txn_id':txid,
'txn':tx,
'redeem_script':redeemScriptCopy
}
def create_reveal_transaction(
private_key, # the private key
input_txn, # the input transaction hash as string
input_index, # the output index
input_balance, # current balance of input, in satoshis
destination_address, # string, currently only supports P2PKH,
redeemScript # directly from the create_commit_transaction function
):
SelectParams('mainnet')
seckey = CBitcoinSecret(private_key)
# lx() takes *little-endian* hex and converts it to bytes; in Bitcoin
# transaction hashes are shown little-endian rather than the usual big-endian.
txid = lx(input_txn)
vout = input_index
# Set postage amount
postage_amount = 333
# Create the txin structure, which includes the outpoint. The scriptSig
# defaults to being empty as is necessary for spending a P2WSH output.
txin = CMutableTxIn(COutPoint(txid, vout))
# Specify a destination address and create the txout.
destination_address = CBitcoinAddress(destination_address).to_scriptPubKey()
txout = CMutableTxOut(postage_amount, destination_address)
# Create the unsigned transaction.
tx = CMutableTransaction([txin], [txout])
# Calculate the signature hash for that transaction.
sighash = SignatureHash(script=redeemScript, txTo=tx, inIdx=0, hashtype=SIGHASH_ALL, amount=input_balance, sigversion=SIGVERSION_WITNESS_V0)
# Now sign it. We have to append the type of signature we want to the end, in
# this case the usual SIGHASH_ALL.
sig = seckey.sign(sighash) + bytes([SIGHASH_ALL])
# Create the txout. This time we create the scriptPubKey from a Bitcoin address.
# Prepare the custom data to push
data1 = "ord".encode('utf-8')
data2 = content_type.encode('utf-8')
data3 = bytes(inscription_bytes)
# Your arbitrary data, not encapsulated in any script
# arbitrary_data = CScript([OP_FALSE, OP_IF, data1, b'\x01', data2, OP_0, data3, OP_ENDIF])
# Construct a witness for this P2WSH transaction and add to tx.
witness = CScriptWitness([sig, redeemScript])
tx.wit = CTxWitness([CTxInWitness(witness)])
txn_id = b2lx(tx.GetTxid())
raw_transaction = b2x(tx.serialize())
txn_cost_in_sats = tx.calc_weight() / 4 * 50
return {
'raw_txn':raw_transaction,
'txn_cost':txn_cost_in_sats,
'txn_id':txn_id,
'txn':tx
}
def broadcast_transaction(raw_transaction):
response = requests.post('https://blockstream.info/api/tx', data=raw_transaction)
return response
# Example of usage inscribing
# First generate a private key. DO NOT lose this. DO NOT leave in plain text.
# Save this somewhere secure.
private_key = generate_private_key()
# Generate a bitcoin address. It only generates P2PKH address right now.
# This addressed is completely controlled via the private key you just created.
bitcoin_address = create_bitcoin_address(private_key)
# Send Bitcoin to your Bitcoin address. 100k sats is sufficient for inscription testing purposes.
input_txn = '50a61f7458663ae3e3177120c7bf14526dccbaa3f1bb7f940b892090123fc60e' # the txn ID from your send
input_index = 1 # usually it is 0
input_balance = 55334# amount you sent over
content_type = 'text/plain;charset=utf-8'
inscription_bytes = 'Hello, world! - with love, P2WSH'.encode('utf-8')
original_address = str(bitcoin_address)
commit_fee = 15000 # this will give you a high sats/vb - good for testing
reveal_fee = 7000 # this will give you a high sats/vb - good for testing
commit_response = create_commit_transaction(
private_key,
input_txn, # transaction id
input_index, # output index
input_balance, # in sats
content_type, # mime type for your inscription content as string
inscription_bytes, # actual inscription content in bytes
original_address, # where to return extra Bitcoin, must be P2PKH for now
commit_fee, # how much to pay in total sats
reveal_fee #
)
broadcast_response = broadcast_transaction(commit_response['raw_txn'])
print(broadcast_response.text)
input_txn = '5d3b4b61c2c4ecd5699c799643d7915dd319400879b33ecc03c517b6d814e44f'
input_index = 0
input_balance = 7333
destination_address = 'bc1q2fh629l329660mlervw60v9pmkeys4n2hafcvw'
redeemScript = commit_response['redeem_script']
reveal_response = create_reveal_transaction(
private_key,
input_txn, # the input transaction hash as string
input_index, # the output index
input_balance, # current balance of input, in satoshis
destination_address, # string, currently only supports P2PKH,
redeemScript # directly from the create_commit_transaction function
)
broadcast_response2 = broadcast_transaction(reveal_response['raw_txn'])
print(broadcast_response2.text)
@bodily11
Copy link
Author

This gist is available with an open source MIT license, so please use it however you would like without attribution or restrictions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment