Skip to content

Commit a93de41

Browse files
committed
Add example to publish text files in the blockchain
As published by tx 1e47936f37e71b98e8bafe51ddc902d59c1318bc556329ba4ab1996981785292
1 parent 8ee393d commit a93de41

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed

examples/publish-text.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (C) 2015 Peter Todd
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
23+
# WARNING: Do not run this on a wallet with a non-trivial amount of BTC. This
24+
# utility has had very little testing and is being published as a
25+
# proof-of-concept only.
26+
27+
# Requires python-bitcoinlib w/ sendmany support:
28+
#
29+
# https://github.com/petertodd/python-bitcoinlib/commit/6a0a2b9429edea318bea7b65a68a950cae536790
30+
31+
import sys
32+
if sys.version_info.major < 3:
33+
sys.stderr.write('Sorry, Python 3.x required by this example.\n')
34+
sys.exit(1)
35+
36+
import argparse
37+
import hashlib
38+
import logging
39+
import sys
40+
import os
41+
42+
import bitcoin.rpc
43+
from bitcoin.core import *
44+
from bitcoin.core.script import *
45+
from bitcoin.wallet import *
46+
47+
parser = argparse.ArgumentParser(
48+
description="Publish text in the blockchain, suitably padded for easy recovery with strings",
49+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
50+
51+
parser.add_argument('-n', action='store_true',
52+
dest='dryrun',
53+
help="Dry-run; don't actually send the transactions")
54+
parser.add_argument("-q","--quiet",action="count",default=0,
55+
help="Be more quiet.")
56+
parser.add_argument("-v","--verbose",action="count",default=0,
57+
help="Be more verbose. Both -v and -q may be used multiple times.")
58+
parser.add_argument("--min-len",action="store",type=int,default=20,
59+
help="Minimum text length; shorter text is padded to this length")
60+
parser.add_argument("-f","--fee-per-kb",action="store",type=float,default=0.0002,
61+
help="Fee-per-KB")
62+
parser.add_argument("-k","--privkey",action="store",type=str,default=None,
63+
help="Specify private key")
64+
65+
net_parser = parser.add_mutually_exclusive_group()
66+
net_parser.add_argument('-t','--testnet', action='store_true',
67+
dest='testnet',
68+
help='Use testnet')
69+
net_parser.add_argument('-r','--regtest', action='store_true',
70+
dest='regtest',
71+
help='Use regtest')
72+
73+
parser.add_argument('fd', type=argparse.FileType('rb'), metavar='FILE',
74+
help='Text file')
75+
76+
args = parser.parse_args()
77+
78+
# Setup logging
79+
args.verbosity = args.verbose - args.quiet
80+
if args.verbosity == 0:
81+
logging.root.setLevel(logging.INFO)
82+
elif args.verbosity >= 1:
83+
logging.root.setLevel(logging.DEBUG)
84+
elif args.verbosity == -1:
85+
logging.root.setLevel(logging.WARNING)
86+
elif args.verbosity <= -2:
87+
logging.root.setLevel(logging.ERROR)
88+
89+
if args.testnet:
90+
bitcoin.SelectParams('testnet')
91+
elif args.regtest:
92+
bitcoin.SelectParams('regtest')
93+
94+
proxy = bitcoin.rpc.Proxy()
95+
96+
if args.privkey is None:
97+
args.privkey = CBitcoinSecret.from_secret_bytes(os.urandom(32))
98+
99+
else:
100+
args.privkey = CBitcoinSecret(args.privkey)
101+
102+
logging.info('Using keypair %s %s' % (b2x(args.privkey.pub), args.privkey))
103+
104+
# Turn the text file into padded lines
105+
if args.fd is sys.stdin:
106+
# work around a bug where even though we specified binary encoding we get
107+
# the sys.stdin instead.
108+
args.fd = sys.stdin.buffer
109+
padded_lines = [b'\x00' + line.rstrip().ljust(args.min_len) + b'\x00' for line in args.fd.readlines()]
110+
111+
scripts = []
112+
while padded_lines:
113+
def make_scripts(lines, n):
114+
# The n makes sure every p2sh addr is unique; the pubkey lets us
115+
# control the order the vin order vs. just using hashlocks.
116+
redeemScript = []
117+
for chunk in reversed(lines):
118+
if len(chunk) > MAX_SCRIPT_ELEMENT_SIZE:
119+
parser.exit('Lines must be less than %d characters; got %d characters' %\
120+
(MAX_SCRIPT_ELEMENT_SIZE, len(chunk)))
121+
redeemScript.extend([OP_HASH160, Hash160(chunk), OP_EQUALVERIFY])
122+
redeemScript = CScript(redeemScript +
123+
[args.privkey.pub, OP_CHECKSIGVERIFY,
124+
n, OP_DROP, # deduplicate push dropped to meet BIP62 rules
125+
OP_DEPTH, 0, OP_EQUAL]) # prevent scriptSig malleability
126+
127+
return CScript(lines) + redeemScript, redeemScript
128+
129+
scriptSig = redeemScript = None
130+
for i in range(len(padded_lines)):
131+
next_scriptSig, next_redeemScript = make_scripts(padded_lines[0:i+1], len(scripts))
132+
133+
# FIXME: magic numbers!
134+
if len(next_redeemScript) > 520 or len(next_scriptSig) > 1600-100:
135+
padded_lines = padded_lines[i:]
136+
break
137+
138+
else:
139+
scriptSig = next_scriptSig
140+
redeemScript = next_redeemScript
141+
142+
else:
143+
padded_lines = []
144+
145+
scripts.append((scriptSig, redeemScript))
146+
147+
# pay to the redeemScripts to make them spendable
148+
149+
# the 41 accounts for the size of the CTxIn itself
150+
payments = {P2SHBitcoinAddress.from_redeemScript(redeemScript):int(((len(scriptSig)+41)/1000 * args.fee_per_kb)*COIN)
151+
for scriptSig, redeemScript in scripts}
152+
153+
prevouts_by_scriptPubKey = None
154+
if not args.dryrun:
155+
txid = proxy.sendmany('', payments, 0)
156+
157+
logging.info('Sent pre-pub tx: %s' % b2lx(txid))
158+
159+
tx = proxy.getrawtransaction(txid)
160+
161+
prevouts_by_scriptPubKey = {txout.scriptPubKey:COutPoint(txid, i) for i, txout in enumerate(tx.vout)}
162+
163+
else:
164+
prevouts_by_scriptPubKey = {redeemScript.to_p2sh_scriptPubKey():COutPoint(b'\x00'*32, i)
165+
for i, (scriptSig, redeemScript) in enumerate(scripts)}
166+
logging.debug('Payments: %r' % payments)
167+
logging.info('Total cost: %s BTC' % str_money_value(sum(amount for addr, amount in payments.items())))
168+
169+
# Create unsigned tx for SignatureHash
170+
171+
# By paying this rather than an OP_RETURN the tx shows up on bc.i, convenient
172+
# for determining propagation; inception for the lulz.
173+
#
174+
# FIXME: these 600 satoshi's aren't taken into account above...
175+
vout = [CTxOut(600, CScript().to_p2sh_scriptPubKey().to_p2sh_scriptPubKey())]
176+
#vout = [CTxOut(0, CScript([OP_RETURN]))]
177+
178+
unsigned_vin = []
179+
for scriptSig, redeemScript in scripts:
180+
scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
181+
182+
txin = CTxIn(prevouts_by_scriptPubKey[scriptPubKey])
183+
unsigned_vin.append(txin)
184+
unsigned_tx = CTransaction(unsigned_vin, vout)
185+
186+
# Sign!
187+
signed_vin = []
188+
for i, (scriptSig, redeemScript) in enumerate(scripts):
189+
sighash = SignatureHash(redeemScript, unsigned_tx, i, SIGHASH_NONE)
190+
sig = args.privkey.sign(sighash) + bytes([SIGHASH_NONE])
191+
192+
signed_scriptSig = CScript([sig] + list(scriptSig))
193+
194+
txin = CTxIn(unsigned_vin[i].prevout, signed_scriptSig)
195+
196+
signed_vin.append(txin)
197+
198+
signed_tx = CTransaction(signed_vin, vout)
199+
200+
if args.dryrun:
201+
serialized_tx = signed_tx.serialize()
202+
logging.info('tx size: %d bytes' % len(serialized_tx))
203+
logging.debug('hex: %s' % b2x(serialized_tx))
204+
205+
else:
206+
# FIXME: the tx could be too long here, but there's no way to get sendmany
207+
# to *not* broadcast the transaction first. This is a proof-of-concept, so
208+
# punting.
209+
210+
logging.debug('Sending publish tx, hex: %s' % b2x(signed_tx.serialize()))
211+
txid = proxy.sendrawtransaction(signed_tx)
212+
logging.info('Sent publish tx: %s' % b2lx(txid))
213+

0 commit comments

Comments
 (0)