From 2385657542a01288ce37f17423d5b8f132cdc52b Mon Sep 17 00:00:00 2001 From: Mark Russinovich Date: Sun, 20 Feb 2022 10:17:10 -0800 Subject: [PATCH] Added BTGC marker and file name to OP_RETURN --- main.py | 47 ++++++++++++++++++++++++++++--------- src/decode.py | 65 +++++++++++++++++++++++++++++++++++++++++++-------- src/encode.py | 35 +++++++++++++++++++++++---- 3 files changed, 122 insertions(+), 25 deletions(-) diff --git a/main.py b/main.py index 5301fa5..5e34d16 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,27 @@ - +# +# BTC Graffiti +# by Mark Russinovich +# +# Copyright (c) 2022 by Mark Russinovich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# from email.mime import image import sys from utils.transaction import * @@ -18,12 +41,11 @@ # parse_commandline # read arguments # -def parse_commandline(): - parser = argparse.ArgumentParser(prog='btcgraffiti', description='A script that encodes the specified image and posts a transaction to the Bitcoin blockchain') - parser.add_argument('file', metavar='file', help='name of file to post or to decode into') - group = parser.add_mutually_exclusive_group() - group.add_argument('--key', "-k", help='Account key' ) - group.add_argument('--transaction', "-tx", help='Transaction to decode' ) +def parse_commandline(descriptiontext): + parser = argparse.ArgumentParser(prog='btcgraffiti', + description=descriptiontext) + parser.add_argument('fileortransaction', metavar='', help='name of file to post or to decode into') + parser.add_argument('--key', "-k", help='Account key. Required for encoding data' ) parser.add_argument('--net', "-n", default='main', choices=['main', 'test'], help='Either \'test\' or \'main\'' ) args = parser.parse_args() return args @@ -34,14 +56,17 @@ def parse_commandline(): # def main(): sys.tracebacklimit=0 - args = parse_commandline() + descriptiontext='Write data into Bitcoin transactions and read data from them.' + print( '\nBTC Graffiti - by Mark Russinovich (@markrussinovich)') + print( descriptiontext, '\n') + args = parse_commandline( descriptiontext ) # either encode or decode if args.key != None: - encode_to_btc( args.key, args.net, args.file ) + encode_to_btc( args.key, args.net, args.fileortransaction ) else: - decode_from_btc( args.transaction, args.net, args.file ) - + decode_from_btc( args.fileortransaction, args.net ) + print('') if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/decode.py b/src/decode.py index 726b7b1..bb753e4 100644 --- a/src/decode.py +++ b/src/decode.py @@ -1,3 +1,27 @@ +# +# BTC Graffiti +# by Mark Russinovich +# +# Copyright (c) 2022 by Mark Russinovich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# from email.mime import image import sys from src.encode import HASH_SIZE @@ -30,16 +54,35 @@ def decode_txout( txout: TxOut ): return None return txout.script_pubkey.cmds[2] +# +# verify_btcgmarker +# +def verify_btcgmarker( txouts ): + for i in range(len(txouts)): + cmds = txouts[i].script_pubkey.cmds + if len(cmds) == 2 and isinstance(cmds[0], int) and OP_CODE_NAMES[cmds[0]] == 'OP_RETURN': + return txouts[i].script_pubkey.cmds[1] + return None # # decode_file -# decode the transaction outputs into a file +# decode the transaction outputs into a file. +# returns the OP_RETURN message # -def decode_file( txouts, file ): +def decode_file( txouts ): filebytes = [] - # ignore last txout which is the miner fee - for i in range(len(txouts)-1): + # make sure this is one of ours by looking for marker in OP_RETURN + btcgmarker = verify_btcgmarker( txouts ) + if btcgmarker == None: + print( 'Error: Not a BTC Graffiti transaction.') + quit() + filename = str(btcgmarker[5:], 'utf-8') + print( 'Writing encoded data to', filename) + + # ignore last two txouts: last one is the OP_RETURN message + # and second to last is remainding unspent + for i in range(len(txouts)-2): filebytes += bytes(decode_txout(txouts[i])) filelen = decode_int(BytesIO(bytes(filebytes[0:])), 4) @@ -47,18 +90,20 @@ def decode_file( txouts, file ): print('Error: Unrecognized encoding.') quit() try: - f = open(file, 'wb') + f = open(filename, 'wb') f.write( bytes(filebytes[4:4+filelen])) + f.close() except IOError as e: - print('Error writing ', file, ': ', e ) + print('Error writing ', filename, ': ', e ) raise + return filename # # decode_from_btc # decode a transaction to extract media content # -def decode_from_btc( tx, net, file ): +def decode_from_btc( tx, net ): print( 'Reading transaction...') if net == 'main': txbytestring = NetworkAPI.get_transaction_by_id( tx ) @@ -67,6 +112,6 @@ def decode_from_btc( tx, net, file ): txbytes = bytes.fromhex(txbytestring) tx = Tx.decode(BytesIO(txbytes)) - print( 'Writing encoded data to file...') - decode_file( tx.tx_outs, file ) - print('Transaction decoded to ', file) + # writ to file + filename = decode_file( tx.tx_outs ) + print('Transaction decoded to', str(filename) ) diff --git a/src/encode.py b/src/encode.py index 7da8f23..e9b59c6 100644 --- a/src/encode.py +++ b/src/encode.py @@ -1,3 +1,27 @@ +# +# BTC Graffiti +# by Mark Russinovich +# +# Copyright (c) 2022 by Mark Russinovich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# from email.mime import image import sys from utils.transaction import * @@ -87,16 +111,19 @@ def encode_to_btc( key, net, file ): key = PrivateKeyTestnet( key ) else: key = PrivateKey( key ) - print('Looking up account balance for ', key.address, '...' ) + print('Looking up account balance for', key.address, '...' ) if( float(key.get_balance('btc')) < 0.0001 ): - print( 'Not enough funds in account', key.address, ' for transaction:\n') + print( 'Not enough funds in account', key.address, 'for transaction:\n') # fire off the transaction! try: - txid = key.send( txouts ) + # save marker and file name in OP_RETURN + filepath = os.path.split(file) + btcgmessage='BTGC:'+filepath[1] + txid = key.send( txouts, message=btcgmessage ) except Exception as inst: print( inst ) return - print('File encoded to transaction ', txid ) + print('File encoded to transaction', txid ) return \ No newline at end of file