Skip to content

Instantly share code, notes, and snippets.

@dagurval

dagurval/eatbch.py

Created Jan 3, 2020
Embed
What would you like to do?
Accept and forward Bitcoin Cash transactions
#!/usr/bin/env python3
import hashlib
import qrcode
import asyncio
from bitcoincash.core import b2x, lx, COutPoint, CMutableTxOut,\
CMutableTxIn, CMutableTransaction
from bitcoincash.core.script import CScript, SignatureHash, OP_RETURN, \
SIGHASH_ALL, SIGHASH_FORKID
from bitcoincash.core.scripteval import VerifyScript
from bitcoincash.wallet import CBitcoinAddress, CBitcoinSecret, P2PKHBitcoinAddress
from bitcoincash.electrum import Electrum
EATBCH_VE = "bitcoincash:pp8skudq3x5hzw8ew7vzsw8tn4k8wxsqsv0lt0mf3g"
EATBCH_SS = "bitcoincash:qrsrvtc95gg8rrag7dge3jlnfs4j9pe0ugrmeml950"
async def main():
cli = Electrum()
await cli.connect()
# Create an insecure brainwallet
h = hashlib.sha256(b'replace this please or get coins stolen').digest()
private_key = CBitcoinSecret.from_secret_bytes(h)
address = P2PKHBitcoinAddress.from_pubkey(private_key.pub)
# Output a QR code for the address in the terminal
qr = qrcode.QRCode()
qr.add_data(str(address).upper())
qr.print_ascii()
print(str(address))
try:
while True:
await forward_loop(cli, private_key, address)
import time
time.sleep(5)
finally:
await cli.close()
async def forward_loop(cli, private_key, address):
# Get list of spendable coins in address
coins = await cli.RPC(
'blockchain.scripthash.listunspent',
address.to_scriptHash())
if not len(coins):
return
# Build our new transaction from scratch.
# * Use all the coins we've received as inputs
# * Create three outputs: One for EatBCH Venezuela,
# one for South Sudan and an OP_RETRUN greeting
# * Sign inputs with Schnorr
tx = CMutableTransaction()
# Store input amounts for later
amounts = []
# All coins received as inputs
for c in coins:
tx_input = CMutableTxIn(COutPoint(lx(c['tx_hash']), c['tx_pos']))
# This dummy scriptSig makes fee calculation simple.
# We know that the Schnorr signature exactly 65 bytes.
tx_input.scriptSig = CScript([b'0' * 65, private_key.pub])
tx.vin.append(tx_input)
amounts.append(c['value']) # store for later
# Dummy output amount (nValue). We need to calculate fee
# before setting the actual amount.
for addr in (CBitcoinAddress(EATBCH_VE), CBitcoinAddress(EATBCH_SS)):
tx_output = CMutableTxOut(nValue = -1, scriptPubKey = addr.to_scriptPubKey())
tx.vout.append(tx_output)
# For fun, let's add a small OP_RETURN greeting as well
tx.vout.append(CMutableTxOut(nValue = 0, scriptPubKey = CScript(
[OP_RETURN, b'Happy new year 2020!'])))
total = sum(amounts)
fee = len(tx.serialize())
if total - fee < 2000:
# The amount is tiny, lets wait for more coins
return
# Now that we know the fee, we can:
# * Update output values
# * Hash and sign the inputs
# Update output values
total -= fee
half = total // 2 # // is integer division
tx.vout[0].nValue = half # EatBCH VE
tx.vout[1].nValue = half # EatBCH SS
# Hash and sign inputs
flags = SIGHASH_ALL | SIGHASH_FORKID
for i in range(0, len(tx.vin)):
sighash = SignatureHash(
address.to_scriptPubKey(),
txTo = tx,
inIdx = i,
hashtype = flags,
amount = amounts[i])
signature = private_key.signSchnorr(sighash) + bytes([flags])
tx.vin[i].scriptSig = CScript([signature, private_key.pub])
# Optional, but useful for developers: Verify that the input is valid.
VerifyScript(
tx.vin[i].scriptSig,
address.to_scriptPubKey(),
tx, i, amount = amounts[i])
# We're playing with money here, so lets assert for safety.
# + 1 off is OK because of the integer division above.
assert sum(o.nValue for o in tx.vout) + 1 + fee >= sum(amounts)
assert len(tx.serialize()) == fee # 1 sat/byte fee
print("Received {} satoshis in {} coins!\n"\
"Forwarding {} to EatBCH VE and {} to EatBCH SE. Tx fee {}.".format(
sum(amounts), len(coins), tx.vout[0].nValue, tx.vout[1].nValue, fee))
# Done! Broadcast to the network.
print("Broadcasting transaction...")
print("Result: {}".format(
await cli.RPC('blockchain.transaction.broadcast', b2x(tx.serialize()))))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment