# satoshi_insertion_tool.py -rw-r--r-- 4.3 KiB View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/python3
#
# File insertion tool for Bitcoin
# Requires git://github.com/jgarzik/python-bitcoinrpc.git
#
# (c) 2013 Satoshi Nakamoto All Rights Reserved
#
# UNAUTHORIZED DUPLICATION AND/OR USAGE OF THIS PROGRAM IS PROHIBITED BY US AND INTERNATIONAL COPYRIGHT LAW

import io
import jsonrpc
import os
import random
import struct
import sys
from binascii import crc32,hexlify,unhexlify
from decimal import Decimal

if len(sys.argv) < 5:
    print(\
"""\
Usage: %s <file> <dest addr> <dest amount> {<fee-per-kb>}

Set BTCRPCURL=http://user:pass@localhost:portnum""" % sys.argv[0], file=sys.stderr)
    sys.exit()

COIN = 100000000

def unhexstr(str):
    return unhexlify(str.encode('utf8'))

proxy = jsonrpc.ServiceProxy(os.environ['BTCRPCURL'])

def select_txins(value):
    unspent = list(proxy.listunspent())
    random.shuffle(unspent)

    r = []
    total = 0
    for tx in unspent:
        total += tx['amount']
        r.append(tx)

        if total >= value:
            break

    if total < value:
        return None
    else:
        return (r, total)

def varint(n):
    if n < 0xfd:
        return bytes([n])
    elif n < 0xffff:
        return b'\xfd' + struct.pack('<H',n)
    else:
        assert False

def packtxin(prevout, scriptSig, seq=0xffffffff):
    return prevout[0][::-1] + struct.pack('<L',prevout[1]) + varint(len(scriptSig)) + scriptSig + struct.pack('<L', seq)

def packtxout(value, scriptPubKey):
    return struct.pack('<Q',int(value*COIN)) + varint(len(scriptPubKey)) + scriptPubKey

def packtx(txins, txouts, locktime=0):
    r = b'\x01\x00\x00\x00' # version
    r += varint(len(txins))

    for txin in txins:
        r += packtxin((unhexstr(txin['txid']),txin['vout']), b'')

    r += varint(len(txouts))

    for (value, scriptPubKey) in txouts:
        r += packtxout(value, scriptPubKey)

    r += struct.pack('<L', locktime)
    return r

OP_CHECKSIG = b'\xac'
OP_CHECKMULTISIG = b'\xae'
OP_PUSHDATA1 = b'\x4c'
OP_DUP = b'\x76'
OP_HASH160 = b'\xa9'
OP_EQUALVERIFY = b'\x88'
def pushdata(data):
    assert len(data) < OP_PUSHDATA1[0]
    return bytes([len(data)]) + data

def pushint(n):
    assert 0 < n <= 16
    return bytes([0x51 + n-1])


def addr2bytes(s):
    digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
    n = 0
    for c in s:
        n *= 58
        if c not in digits58:
            raise ValueError
        n += digits58.index(c)

    h = '%x' % n
    if len(h) % 2:
        h = '0' + h

    for c in s:
        if c == digits58[0]:
            h = '00' + h
        else:
            break
    return unhexstr(h)[1:-4] # skip version and checksum

def checkmultisig_scriptPubKey_dump(fd):
    data = fd.read(65*3)
    if not data:
        return None

    r = pushint(1)

    n = 0
    while data:
        chunk = data[0:65]
        data = data[65:]

        if len(chunk) < 33:
            chunk += b'\x00'*(33-len(chunk))
        elif len(chunk) < 65:
            chunk += b'\x00'*(65-len(chunk))

        r += pushdata(chunk)
        n += 1

    r += pushint(n) + OP_CHECKMULTISIG
    return r


(txins, change) = select_txins(0)

txouts = []

data = open(sys.argv[1],'rb').read()
data = struct.pack('<L', len(data)) + struct.pack('<L', crc32(data)) + data
fd = io.BytesIO(data)

while True:
    scriptPubKey = checkmultisig_scriptPubKey_dump(fd)

    if scriptPubKey is None:
        break

    value = Decimal(1/COIN)
    txouts.append((value, scriptPubKey))

    change -= value

# dest output
out_value = Decimal(sys.argv[3])
change -= out_value
txouts.append((out_value, OP_DUP + OP_HASH160 + pushdata(addr2bytes(sys.argv[2])) + OP_EQUALVERIFY + OP_CHECKSIG))

# change output
change_addr = proxy.getnewaddress()
txouts.append([change, OP_DUP + OP_HASH160 + pushdata(addr2bytes(change_addr)) + OP_EQUALVERIFY + OP_CHECKSIG])

tx = packtx(txins, txouts)
signed_tx = proxy.signrawtransaction(hexlify(tx).decode('utf8'))

FEEPERKB = Decimal(0.001)
try:
    FEEPERKB = Decimal(sys.argv[4])
except IndexError:
    pass
fee = Decimal(len(signed_tx['hex'])/1000) * FEEPERKB
change -= fee
txouts[-1][0] = change

tx = packtx(txins, txouts)
signed_tx = proxy.signrawtransaction(hexlify(tx).decode('utf8'))
assert signed_tx['complete']

print('Size: %d  Fee: %2.8f' % (len(signed_tx['hex'])/2,fee),file=sys.stderr)

if False:
    print(proxy.sendrawtransaction(signed_tx['hex']))
else:
    print(signed_tx)