Created
January 3, 2020 10:28
-
-
Save dagurval/d34076523a6ebb794c1ee6822e5fa26c to your computer and use it in GitHub Desktop.
Accept and forward Bitcoin Cash transactions
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
#!/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