Created
July 21, 2023 12:25
-
-
Save bodily11/58773d64b9d0e3e8a18a81ae665fa44e to your computer and use it in GitHub Desktop.
Script to inscribe using P2WSH
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This gist is available with an open source MIT license, so please use it however you would like without attribution or restrictions.