Skip to content

Commit

Permalink
Added BTGC marker and file name to OP_RETURN
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark Russinovich committed Feb 20, 2022
1 parent 47e84e4 commit 2385657
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 25 deletions.
47 changes: 36 additions & 11 deletions main.py
Original file line number Diff line number Diff line change
@@ -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 *
Expand All @@ -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='<file|transaction>', 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
Expand All @@ -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()
65 changes: 55 additions & 10 deletions src/decode.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,35 +54,56 @@ 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)

if len(filebytes)-4 > filelen + HASH_SIZE or len(filebytes)-4 < filelen - HASH_SIZE:
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 )
Expand All @@ -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) )
35 changes: 31 additions & 4 deletions src/encode.py
Original file line number Diff line number Diff line change
@@ -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 *
Expand Down Expand Up @@ -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

0 comments on commit 2385657

Please sign in to comment.