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