4242 lines
142 KiB
Python
4242 lines
142 KiB
Python
#!/usr/bin/env python
|
|
#-*- coding: utf-8 -*-
|
|
from __future__ import print_function
|
|
pywversion="2.2"
|
|
never_update=False
|
|
|
|
#
|
|
# jackjack's pywallet.py
|
|
# https://github.com/jackjack-jj/pywallet
|
|
# forked from Joric's pywallet.py
|
|
#
|
|
|
|
|
|
import sys
|
|
PY3 = sys.version_info.major > 2
|
|
|
|
import warnings
|
|
def warning_on_one_line(message, category, filename, lineno, file=None, line=None):
|
|
return '%s:%s: %s: %s\n' % (filename, lineno, category.__name__, message)
|
|
warnings.formatwarning = warning_on_one_line
|
|
if PY3:
|
|
warnings.warn("Python 3 support is still experimental, you may encounter bugs")
|
|
import _thread as thread
|
|
import functools
|
|
raw_input = input
|
|
xrange = range
|
|
long = int
|
|
unicode = str
|
|
reduce = functools.reduce
|
|
else:
|
|
import thread
|
|
|
|
missing_dep = []
|
|
|
|
try:
|
|
from bsddb.db import *
|
|
except:
|
|
try:
|
|
from bsddb3.db import *
|
|
except:
|
|
missing_dep.append('bsddb')
|
|
|
|
import os, sys, time, re
|
|
pyw_filename = os.path.basename(__file__)
|
|
pyw_path = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
try:
|
|
import simplejson as json
|
|
except:
|
|
import json
|
|
|
|
import bisect
|
|
import itertools
|
|
import unicodedata
|
|
import hmac
|
|
import getpass
|
|
import logging
|
|
import struct
|
|
import traceback
|
|
import socket
|
|
import types
|
|
import string
|
|
import hashlib
|
|
import random
|
|
import urllib
|
|
import math
|
|
import base64
|
|
import collections
|
|
import weakref
|
|
import binascii
|
|
from types import MethodType
|
|
import unittest
|
|
|
|
from datetime import datetime
|
|
from subprocess import *
|
|
|
|
import os
|
|
import os.path
|
|
import platform
|
|
|
|
def ordsix(x):
|
|
if x.__class__ == int:return x
|
|
return ord(x)
|
|
def chrsix(x):
|
|
if not(x.__class__ in [int, long]):return x
|
|
if PY3:return bytes([x])
|
|
return chr(x)
|
|
|
|
def str_to_bytes(k):
|
|
if k.__class__ == str and not hasattr(k, 'decode'):
|
|
return bytes(k, 'ascii')
|
|
return k
|
|
def bytes_to_str(k):
|
|
if k.__class__ == bytes:
|
|
return k.decode()
|
|
if k.__class__ == unicode:
|
|
return bytes_to_str(k.encode())
|
|
return k
|
|
|
|
class Bdict(dict):
|
|
def __init__(self, *a, **kw):
|
|
super(Bdict, self).__init__(*a, **kw)
|
|
for k, v in self.copy().items():
|
|
try:del self[k]
|
|
except:pass
|
|
self[bytes_to_str(k)] = v
|
|
def update(self, *a, **kw):
|
|
other = self.__class__(*a, **kw)
|
|
return super(Bdict, self).update(other)
|
|
def pop(self, k, *a):
|
|
return super(Bdict, self).pop(bytes_to_str(k), *a)
|
|
def get(self, k, default=None):
|
|
return super(Bdict, self).get(bytes_to_str(k), default)
|
|
def __getitem__(self, k):
|
|
return super(Bdict, self).__getitem__(bytes_to_str(k))
|
|
def __setitem__(self, k, v):
|
|
return super(Bdict, self).__setitem__(bytes_to_str(k), v)
|
|
def __contains__(self, k):
|
|
return super(Bdict, self).__contains__(bytes_to_str(k))
|
|
def __repr__(self):
|
|
return '%s(%s)'%(self.__class__.__name__, super(Bdict, self).__repr__())
|
|
|
|
max_version = 81000
|
|
json_db = Bdict({})
|
|
private_keys = []
|
|
private_hex_keys = []
|
|
passphrase = ""
|
|
global_merging_message = ["",""]
|
|
CNT = collections.namedtuple
|
|
|
|
balance_site = 'https://blockchain.info/q/addressbalance/'
|
|
backup_balance_site ='https://api.blockcypher.com/v1/btc/main/addrs/'
|
|
|
|
aversions = {}
|
|
for i in range(256):
|
|
aversions[i] = "version %d" % i;
|
|
aversions[0] = 'Bitcoin';
|
|
aversions[48] = 'Litecoin';
|
|
aversions[52] = 'Namecoin';
|
|
aversions[111] = 'Testnet';
|
|
|
|
class Network(collections.namedtuple('Network', 'name p2pkh_prefix p2sh_prefix wif_prefix segwit_hrp')):
|
|
instances = []
|
|
def __init__(self, *a, **kw):
|
|
self.__class__.instances.append(self)
|
|
super(Network, self).__init__()
|
|
def keyinfo(self, *a, **kw):
|
|
pass
|
|
|
|
def eip55(hex_addr):
|
|
if hex_addr[:2] == '0x':hex_addr = hex_addr[2:]
|
|
hex_addr = hex_addr.lower()
|
|
checksummed_buffer = ""
|
|
hashed_address = bytes_to_str(binascii.hexlify(Keccak256(hex_addr).digest()))
|
|
for nibble_index, character in enumerate(hex_addr):
|
|
if character in "0123456789":
|
|
checksummed_buffer += character
|
|
elif character in "abcdef":
|
|
hashed_address_nibble = int(hashed_address[nibble_index], 16)
|
|
if hashed_address_nibble > 7:
|
|
checksummed_buffer += character.upper()
|
|
else:
|
|
checksummed_buffer += character
|
|
else:
|
|
raise ValueError("Unrecognized hex character {} at position {}".format(character, nibble_index))
|
|
return "0x" + checksummed_buffer
|
|
|
|
def ethereum_keyinfo(self, keyinfo, print_info=True):
|
|
ethpubkey = keyinfo.uncompressed_public_key[1:]
|
|
eth_hash = binascii.hexlify(Keccak256(ethpubkey).digest())[-40:]
|
|
eth_addr = '0x' + bytes_to_str(eth_hash)
|
|
if print_info and not keyinfo.compressed:
|
|
print("Ethereum address: %s"%eip55(eth_addr))
|
|
print("Ethereum B58address: %s"%public_key_to_bc_address(eth_hash, 33))
|
|
return CNT('SubKeyInfo', 'addr')(eth_addr)
|
|
|
|
network_bitcoin = Network('Bitcoin', 0, 5, 0x80, 'bc')
|
|
network_bitcoin_testnet3 = Network('Bitcoin-Testnet3', 0x6f, 0xc4, 0xef, 'tb')
|
|
network_ethereum = Network('Ethereum', 0, 5, 0x80, 'eth')
|
|
network_ethereum.keyinfo = MethodType(ethereum_keyinfo, network_ethereum)
|
|
network = network_bitcoin
|
|
def find_network(name):
|
|
for n in Network.instances:
|
|
if n.name.lower() == name.lower():
|
|
return n
|
|
return None
|
|
|
|
wallet_dir = ""
|
|
wallet_name = ""
|
|
|
|
ko = 1e3
|
|
kio = 1024
|
|
Mo = 1e6
|
|
Mio = 1024 ** 2
|
|
Go = 1e9
|
|
Gio = 1024 ** 3
|
|
To = 1e12
|
|
Tio = 1024 ** 4
|
|
|
|
prekeys = [binascii.unhexlify("308201130201010420"), binascii.unhexlify("308201120201010420")]
|
|
postkeys = [binascii.unhexlify("a081a530"), binascii.unhexlify("81a530")]
|
|
|
|
KeyInfo = collections.namedtuple('KeyInfo', 'secret private_key public_key uncompressed_public_key addr wif compressed')
|
|
|
|
def plural(a):
|
|
if a>=2:return 's'
|
|
return ''
|
|
|
|
def systype():
|
|
if platform.system() == "Darwin":return 'Mac'
|
|
elif platform.system() == "Windows":return 'Win'
|
|
return 'Linux'
|
|
|
|
def determine_db_dir():
|
|
if wallet_dir in "":
|
|
if platform.system() == "Darwin":
|
|
return os.path.expanduser("~/Library/Application Support/Bitcoin/")
|
|
elif platform.system() == "Windows":
|
|
return os.path.join(os.environ['APPDATA'], "Bitcoin")
|
|
return os.path.expanduser("~/.bitcoin")
|
|
else:
|
|
return wallet_dir
|
|
|
|
def determine_db_name():
|
|
if wallet_name in "":
|
|
return "wallet.dat"
|
|
else:
|
|
return wallet_name
|
|
|
|
########################
|
|
########################
|
|
|
|
from math import log
|
|
from operator import xor
|
|
from copy import deepcopy
|
|
RoundConstants=[1,32898,0x800000000000808a,0x8000000080008000,32907,2147483649,0x8000000080008081,0x8000000000008009,138,136,2147516425,2147483658,2147516555,0x800000000000008b,0x8000000000008089,0x8000000000008003,0x8000000000008002,0x8000000000000080,32778,0x800000008000000a,0x8000000080008081,0x8000000000008080,2147483649,0x8000000080008008]
|
|
RotationConstants=[[0,1,62,28,27],[36,44,6,55,20],[3,10,43,25,39],[41,45,15,21,8],[18,2,61,56,14]]
|
|
Masks=[(1<<i)-1 for i in range(65)]
|
|
def bits2bytes(x):return(int(x)+7)//8
|
|
def rol(value,left,bits):top=value>>bits-left;bot=(value&Masks[bits-left])<<left;return bot|top
|
|
def ror(value,right,bits):top=value>>right;bot=(value&Masks[right])<<bits-right;return bot|top
|
|
def multirate_padding(used_bytes,align_bytes):
|
|
padlen=align_bytes-used_bytes
|
|
if padlen==0:padlen=align_bytes
|
|
if padlen==1:return[129]
|
|
else:return[1]+[0]*(padlen-2)+[128]
|
|
def keccak_f(state):
|
|
def round(A,RC):
|
|
W,H=state.W,state.H;rangeW,rangeH=state.rangeW,state.rangeH;lanew=state.lanew;zero=state.zero;C=[reduce(xor,A[x])for x in rangeW];D=[0]*W
|
|
for x in rangeW:
|
|
D[x]=C[(x-1)%W]^rol(C[(x+1)%W],1,lanew)
|
|
for y in rangeH:A[x][y]^=D[x]
|
|
B=zero()
|
|
for x in rangeW:
|
|
for y in rangeH:B[y%W][(2*x+3*y)%H]=rol(A[x][y],RotationConstants[y][x],lanew)
|
|
for x in rangeW:
|
|
for y in rangeH:A[x][y]=B[x][y]^~ B[(x+1)%W][y]&B[(x+2)%W][y]
|
|
A[0][0]^=RC
|
|
l=int(log(state.lanew,2));nr=12+2*l
|
|
for ir in xrange(nr):round(state.s,RoundConstants[ir])
|
|
class KeccakState:
|
|
W=5;H=5;rangeW=range(W);rangeH=range(H)
|
|
@staticmethod
|
|
def zero():return[[0]*KeccakState.W for x in KeccakState.rangeH]
|
|
@staticmethod
|
|
def format(st):
|
|
rows=[]
|
|
def fmt(x):return'%016x'%x
|
|
for y in KeccakState.rangeH:
|
|
row=[]
|
|
for x in rangeW:row.append(fmt(st[x][y]))
|
|
rows.append(' '.join(row))
|
|
return '\n'.join(rows)
|
|
@staticmethod
|
|
def lane2bytes(s,w):
|
|
o=[]
|
|
for b in range(0,w,8):o.append(s>>b&255)
|
|
return o
|
|
@staticmethod
|
|
def bytes2lane(bb):
|
|
r=0
|
|
for b in reversed(bb):r=r<<8|b
|
|
return r
|
|
@staticmethod
|
|
def bytes2str(bb):return str_to_bytes('').join(map(chrsix,bb))
|
|
@staticmethod
|
|
def str2bytes(ss):return map(ordsix,ss)
|
|
def __init__(self,bitrate,b):self.bitrate=bitrate;self.b=b;assert self.bitrate%8==0;self.bitrate_bytes=bits2bytes(self.bitrate);assert self.b%25==0;self.lanew=self.b//25;self.s=KeccakState.zero()
|
|
def __str__(self):return KeccakState.format(self.s)
|
|
def absorb(self,bb):
|
|
assert len(bb)==self.bitrate_bytes;bb+=[0]*bits2bytes(self.b-self.bitrate);i=0
|
|
for y in self.rangeH:
|
|
for x in self.rangeW:self.s[x][y]^=KeccakState.bytes2lane(bb[i:i+8]);i+=8
|
|
def squeeze(self):return self.get_bytes()[:self.bitrate_bytes]
|
|
def get_bytes(self):
|
|
out=[0]*bits2bytes(self.b);i=0
|
|
for y in self.rangeH:
|
|
for x in self.rangeW:v=KeccakState.lane2bytes(self.s[x][y],self.lanew);out[i:i+8]=v;i+=8
|
|
return out
|
|
def set_bytes(self,bb):
|
|
i=0
|
|
for y in self.rangeH:
|
|
for x in self.rangeW:self.s[x][y]=KeccakState.bytes2lane(bb[i:i+8]);i+=8
|
|
class KeccakSponge:
|
|
def __init__(self,bitrate,width,padfn,permfn):self.state=KeccakState(bitrate,width);self.padfn=padfn;self.permfn=permfn;self.buffer=[]
|
|
def copy(self):return deepcopy(self)
|
|
def absorb_block(self,bb):assert len(bb)==self.state.bitrate_bytes;self.state.absorb(bb);self.permfn(self.state)
|
|
def absorb(self,s):
|
|
self.buffer+=KeccakState.str2bytes(s)
|
|
while len(self.buffer)>=self.state.bitrate_bytes:self.absorb_block(self.buffer[:self.state.bitrate_bytes]);self.buffer=self.buffer[self.state.bitrate_bytes:]
|
|
def absorb_final(self):padded=self.buffer+self.padfn(len(self.buffer),self.state.bitrate_bytes);self.absorb_block(padded);self.buffer=[]
|
|
def squeeze_once(self):rc=self.state.squeeze();self.permfn(self.state);return rc
|
|
def squeeze(self,l):
|
|
Z=self.squeeze_once()
|
|
while len(Z)<l:Z+=self.squeeze_once()
|
|
return Z[:l]
|
|
class KeccakHash:
|
|
def __init__(self,bitrate_bits,capacity_bits,output_bits):assert bitrate_bits+capacity_bits in(25,50,100,200,400,800,1600);self.sponge=KeccakSponge(bitrate_bits,bitrate_bits+capacity_bits,multirate_padding,keccak_f);assert output_bits%8==0;self.digest_size=bits2bytes(output_bits);self.block_size=bits2bytes(bitrate_bits)
|
|
def __repr__(self):inf=self.sponge.state.bitrate,self.sponge.state.b-self.sponge.state.bitrate,self.digest_size*8;return'<KeccakHash with r=%d, c=%d, image=%d>'%inf
|
|
def copy(self):return deepcopy(self)
|
|
def update(self,s):self.sponge.absorb(s)
|
|
def digest(self):finalised=self.sponge.copy();finalised.absorb_final();digest=finalised.squeeze(self.digest_size);return KeccakState.bytes2str(digest)
|
|
def hexdigest(self):return binascii.hexlify(self.digest())
|
|
@staticmethod
|
|
def preset(bitrate_bits,capacity_bits,output_bits):
|
|
def create(initial_input=None):
|
|
h=KeccakHash(bitrate_bits,capacity_bits,output_bits)
|
|
if not(initial_input is None):h.update(initial_input)
|
|
return h
|
|
return create
|
|
Keccak256 = KeccakHash.preset(1088, 512, 256)
|
|
|
|
########################
|
|
########################
|
|
|
|
########################
|
|
# begin of aes.py code #
|
|
########################
|
|
|
|
# from the SlowAES project, http://code.google.com/p/slowaes (aes.py)
|
|
|
|
def append_PKCS7_padding(s):
|
|
"""return s padded to a multiple of 16-bytes by PKCS7 padding"""
|
|
numpads = 16 - (len(s)%16)
|
|
return s + numpads*chrsix(numpads)
|
|
|
|
def strip_PKCS7_padding(s):
|
|
"""return s stripped of PKCS7 padding"""
|
|
if len(s)%16 or not s:
|
|
raise ValueError("String of len %d can't be PCKS7-padded" % len(s))
|
|
numpads = ordsix(s[-1])
|
|
if numpads > 16:
|
|
raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1])
|
|
return s[:-numpads]
|
|
|
|
class AES(object):
|
|
# valid key sizes
|
|
keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32)
|
|
|
|
# Rijndael S-box
|
|
sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67,
|
|
0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59,
|
|
0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7,
|
|
0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1,
|
|
0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05,
|
|
0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83,
|
|
0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29,
|
|
0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
|
|
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa,
|
|
0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c,
|
|
0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc,
|
|
0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
|
|
0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19,
|
|
0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee,
|
|
0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
|
|
0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
|
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4,
|
|
0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6,
|
|
0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
|
|
0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
|
|
0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e,
|
|
0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1,
|
|
0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0,
|
|
0x54, 0xbb, 0x16]
|
|
|
|
# Rijndael Inverted S-box
|
|
rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3,
|
|
0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f,
|
|
0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54,
|
|
0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b,
|
|
0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24,
|
|
0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8,
|
|
0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d,
|
|
0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
|
|
0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab,
|
|
0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3,
|
|
0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1,
|
|
0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41,
|
|
0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6,
|
|
0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9,
|
|
0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d,
|
|
0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b ,
|
|
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0,
|
|
0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07,
|
|
0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60,
|
|
0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f,
|
|
0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5,
|
|
0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b,
|
|
0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55,
|
|
0x21, 0x0c, 0x7d]
|
|
|
|
def getSBoxValue(self,num):
|
|
"""Retrieves a given S-Box Value"""
|
|
return self.sbox[num]
|
|
|
|
def getSBoxInvert(self,num):
|
|
"""Retrieves a given Inverted S-Box Value"""
|
|
return self.rsbox[num]
|
|
|
|
def rotate(self, word):
|
|
""" Rijndael's key schedule rotate operation.
|
|
|
|
Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d
|
|
Word is an char list of size 4 (32 bits overall).
|
|
"""
|
|
return word[1:] + word[:1]
|
|
|
|
# Rijndael Rcon
|
|
Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
|
|
0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97,
|
|
0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
|
|
0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66,
|
|
0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
|
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
|
|
0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
|
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61,
|
|
0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
|
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
|
|
0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,
|
|
0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
|
|
0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a,
|
|
0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d,
|
|
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
|
|
0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
|
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4,
|
|
0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
|
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08,
|
|
0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
|
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
|
|
0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2,
|
|
0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74,
|
|
0xe8, 0xcb ]
|
|
|
|
def getRconValue(self, num):
|
|
"""Retrieves a given Rcon Value"""
|
|
return self.Rcon[num]
|
|
|
|
def core(self, word, iteration):
|
|
"""Key schedule core."""
|
|
# rotate the 32-bit word 8 bits to the left
|
|
word = self.rotate(word)
|
|
# apply S-Box substitution on all 4 parts of the 32-bit word
|
|
for i in range(4):
|
|
word[i] = self.getSBoxValue(word[i])
|
|
# XOR the output of the rcon operation with i to the first part
|
|
# (leftmost) only
|
|
word[0] = word[0] ^ self.getRconValue(iteration)
|
|
return word
|
|
|
|
def expandKey(self, key, size, expandedKeySize):
|
|
"""Rijndael's key expansion.
|
|
|
|
Expands an 128,192,256 key into an 176,208,240 bytes key
|
|
|
|
expandedKey is a char list of large enough size,
|
|
key is the non-expanded key.
|
|
"""
|
|
# current expanded keySize, in bytes
|
|
currentSize = 0
|
|
rconIteration = 1
|
|
expandedKey = [0] * expandedKeySize
|
|
|
|
# set the 16, 24, 32 bytes of the expanded key to the input key
|
|
for j in range(size):
|
|
expandedKey[j] = key[j]
|
|
currentSize += size
|
|
|
|
while currentSize < expandedKeySize:
|
|
# assign the previous 4 bytes to the temporary value t
|
|
t = expandedKey[currentSize-4:currentSize]
|
|
|
|
# every 16,24,32 bytes we apply the core schedule to t
|
|
# and increment rconIteration afterwards
|
|
if currentSize % size == 0:
|
|
t = self.core(t, rconIteration)
|
|
rconIteration += 1
|
|
# For 256-bit keys, we add an extra sbox to the calculation
|
|
if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16):
|
|
for l in range(4): t[l] = self.getSBoxValue(t[l])
|
|
|
|
# We XOR t with the four-byte block 16,24,32 bytes before the new
|
|
# expanded key. This becomes the next four bytes in the expanded
|
|
# key.
|
|
for m in range(4):
|
|
expandedKey[currentSize] = expandedKey[currentSize - size] ^ \
|
|
t[m]
|
|
currentSize += 1
|
|
|
|
return expandedKey
|
|
|
|
def addRoundKey(self, state, roundKey):
|
|
"""Adds (XORs) the round key to the state."""
|
|
for i in range(16):
|
|
state[i] ^= roundKey[i]
|
|
return state
|
|
|
|
def createRoundKey(self, expandedKey, roundKeyPointer):
|
|
"""Create a round key.
|
|
Creates a round key from the given expanded key and the
|
|
position within the expanded key.
|
|
"""
|
|
roundKey = [0] * 16
|
|
for i in range(4):
|
|
for j in range(4):
|
|
roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j]
|
|
return roundKey
|
|
|
|
def galois_multiplication(self, a, b):
|
|
"""Galois multiplication of 8 bit characters a and b."""
|
|
p = 0
|
|
for counter in range(8):
|
|
if b & 1: p ^= a
|
|
hi_bit_set = a & 0x80
|
|
a <<= 1
|
|
# keep a 8 bit
|
|
a &= 0xFF
|
|
if hi_bit_set:
|
|
a ^= 0x1b
|
|
b >>= 1
|
|
return p
|
|
|
|
#
|
|
# substitute all the values from the state with the value in the SBox
|
|
# using the state value as index for the SBox
|
|
#
|
|
def subBytes(self, state, isInv):
|
|
if isInv: getter = self.getSBoxInvert
|
|
else: getter = self.getSBoxValue
|
|
for i in range(16): state[i] = getter(state[i])
|
|
return state
|
|
|
|
# iterate over the 4 rows and call shiftRow() with that row
|
|
def shiftRows(self, state, isInv):
|
|
for i in range(4):
|
|
state = self.shiftRow(state, i*4, i, isInv)
|
|
return state
|
|
|
|
# each iteration shifts the row to the left by 1
|
|
def shiftRow(self, state, statePointer, nbr, isInv):
|
|
for i in range(nbr):
|
|
if isInv:
|
|
state[statePointer:statePointer+4] = \
|
|
state[statePointer+3:statePointer+4] + \
|
|
state[statePointer:statePointer+3]
|
|
else:
|
|
state[statePointer:statePointer+4] = \
|
|
state[statePointer+1:statePointer+4] + \
|
|
state[statePointer:statePointer+1]
|
|
return state
|
|
|
|
# galois multiplication of the 4x4 matrix
|
|
def mixColumns(self, state, isInv):
|
|
# iterate over the 4 columns
|
|
for i in range(4):
|
|
# construct one column by slicing over the 4 rows
|
|
column = state[i:i+16:4]
|
|
# apply the mixColumn on one column
|
|
column = self.mixColumn(column, isInv)
|
|
# put the values back into the state
|
|
state[i:i+16:4] = column
|
|
|
|
return state
|
|
|
|
# galois multiplication of 1 column of the 4x4 matrix
|
|
def mixColumn(self, column, isInv):
|
|
if isInv: mult = [14, 9, 13, 11]
|
|
else: mult = [2, 1, 1, 3]
|
|
cpy = list(column)
|
|
g = self.galois_multiplication
|
|
|
|
column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \
|
|
g(cpy[2], mult[2]) ^ g(cpy[1], mult[3])
|
|
column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \
|
|
g(cpy[3], mult[2]) ^ g(cpy[2], mult[3])
|
|
column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \
|
|
g(cpy[0], mult[2]) ^ g(cpy[3], mult[3])
|
|
column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \
|
|
g(cpy[1], mult[2]) ^ g(cpy[0], mult[3])
|
|
return column
|
|
|
|
# applies the 4 operations of the forward round in sequence
|
|
def aes_round(self, state, roundKey):
|
|
state = self.subBytes(state, False)
|
|
state = self.shiftRows(state, False)
|
|
state = self.mixColumns(state, False)
|
|
state = self.addRoundKey(state, roundKey)
|
|
return state
|
|
|
|
# applies the 4 operations of the inverse round in sequence
|
|
def aes_invRound(self, state, roundKey):
|
|
state = self.shiftRows(state, True)
|
|
state = self.subBytes(state, True)
|
|
state = self.addRoundKey(state, roundKey)
|
|
state = self.mixColumns(state, True)
|
|
return state
|
|
|
|
# Perform the initial operations, the standard round, and the final
|
|
# operations of the forward aes, creating a round key for each round
|
|
def aes_main(self, state, expandedKey, nbrRounds):
|
|
state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
|
|
i = 1
|
|
while i < nbrRounds:
|
|
state = self.aes_round(state,
|
|
self.createRoundKey(expandedKey, 16*i))
|
|
i += 1
|
|
state = self.subBytes(state, False)
|
|
state = self.shiftRows(state, False)
|
|
state = self.addRoundKey(state,
|
|
self.createRoundKey(expandedKey, 16*nbrRounds))
|
|
return state
|
|
|
|
# Perform the initial operations, the standard round, and the final
|
|
# operations of the inverse aes, creating a round key for each round
|
|
def aes_invMain(self, state, expandedKey, nbrRounds):
|
|
state = self.addRoundKey(state,
|
|
self.createRoundKey(expandedKey, 16*nbrRounds))
|
|
i = nbrRounds - 1
|
|
while i > 0:
|
|
state = self.aes_invRound(state,
|
|
self.createRoundKey(expandedKey, 16*i))
|
|
i -= 1
|
|
state = self.shiftRows(state, True)
|
|
state = self.subBytes(state, True)
|
|
state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
|
|
return state
|
|
|
|
# encrypts a 128 bit input block against the given key of size specified
|
|
def encrypt(self, iput, key, size):
|
|
output = [0] * 16
|
|
# the number of rounds
|
|
nbrRounds = 0
|
|
# the 128 bit block to encode
|
|
block = [0] * 16
|
|
# set the number of rounds
|
|
if size == self.keySize["SIZE_128"]: nbrRounds = 10
|
|
elif size == self.keySize["SIZE_192"]: nbrRounds = 12
|
|
elif size == self.keySize["SIZE_256"]: nbrRounds = 14
|
|
else: return None
|
|
|
|
# the expanded keySize
|
|
expandedKeySize = 16*(nbrRounds+1)
|
|
|
|
# Set the block values, for the block:
|
|
# a0,0 a0,1 a0,2 a0,3
|
|
# a1,0 a1,1 a1,2 a1,3
|
|
# a2,0 a2,1 a2,2 a2,3
|
|
# a3,0 a3,1 a3,2 a3,3
|
|
# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
|
|
#
|
|
# iterate over the columns
|
|
for i in range(4):
|
|
# iterate over the rows
|
|
for j in range(4):
|
|
block[(i+(j*4))] = iput[(i*4)+j]
|
|
|
|
# expand the key into an 176, 208, 240 bytes key
|
|
# the expanded key
|
|
expandedKey = self.expandKey(key, size, expandedKeySize)
|
|
|
|
# encrypt the block using the expandedKey
|
|
block = self.aes_main(block, expandedKey, nbrRounds)
|
|
|
|
# unmap the block again into the output
|
|
for k in range(4):
|
|
# iterate over the rows
|
|
for l in range(4):
|
|
output[(k*4)+l] = block[(k+(l*4))]
|
|
return output
|
|
|
|
# decrypts a 128 bit input block against the given key of size specified
|
|
def decrypt(self, iput, key, size):
|
|
output = [0] * 16
|
|
# the number of rounds
|
|
nbrRounds = 0
|
|
# the 128 bit block to decode
|
|
block = [0] * 16
|
|
# set the number of rounds
|
|
if size == self.keySize["SIZE_128"]: nbrRounds = 10
|
|
elif size == self.keySize["SIZE_192"]: nbrRounds = 12
|
|
elif size == self.keySize["SIZE_256"]: nbrRounds = 14
|
|
else: return None
|
|
|
|
# the expanded keySize
|
|
expandedKeySize = 16*(nbrRounds+1)
|
|
|
|
# Set the block values, for the block:
|
|
# a0,0 a0,1 a0,2 a0,3
|
|
# a1,0 a1,1 a1,2 a1,3
|
|
# a2,0 a2,1 a2,2 a2,3
|
|
# a3,0 a3,1 a3,2 a3,3
|
|
# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
|
|
|
|
# iterate over the columns
|
|
for i in range(4):
|
|
# iterate over the rows
|
|
for j in range(4):
|
|
block[(i+(j*4))] = iput[(i*4)+j]
|
|
# expand the key into an 176, 208, 240 bytes key
|
|
expandedKey = self.expandKey(key, size, expandedKeySize)
|
|
# decrypt the block using the expandedKey
|
|
block = self.aes_invMain(block, expandedKey, nbrRounds)
|
|
# unmap the block again into the output
|
|
for k in range(4):
|
|
# iterate over the rows
|
|
for l in range(4):
|
|
output[(k*4)+l] = block[(k+(l*4))]
|
|
return output
|
|
|
|
class AESModeOfOperation(object):
|
|
|
|
aes = AES()
|
|
|
|
# structure of supported modes of operation
|
|
modeOfOperation = dict(OFB=0, CFB=1, CBC=2)
|
|
|
|
# converts a 16 character string into a number array
|
|
def convertString(self, string, start, end, mode):
|
|
if end - start > 16: end = start + 16
|
|
if mode == self.modeOfOperation["CBC"]: ar = [0] * 16
|
|
else: ar = []
|
|
|
|
i = start
|
|
j = 0
|
|
while len(ar) < end - start:
|
|
ar.append(0)
|
|
while i < end:
|
|
ar[j] = ordsix(string[i])
|
|
j += 1
|
|
i += 1
|
|
return ar
|
|
|
|
# Mode of Operation Encryption
|
|
# stringIn - Input String
|
|
# mode - mode of type modeOfOperation
|
|
# hexKey - a hex key of the bit length size
|
|
# size - the bit length of the key
|
|
# hexIV - the 128 bit hex Initilization Vector
|
|
def encrypt(self, stringIn, mode, key, size, IV):
|
|
if len(key) % size:
|
|
return None
|
|
if len(IV) % 16:
|
|
return None
|
|
# the AES input/output
|
|
plaintext = []
|
|
iput = [0] * 16
|
|
output = []
|
|
ciphertext = [0] * 16
|
|
# the output cipher string
|
|
cipherOut = []
|
|
# char firstRound
|
|
firstRound = True
|
|
if stringIn != None:
|
|
for j in range(int(math.ceil(float(len(stringIn))//16))):
|
|
start = j*16
|
|
end = j*16+16
|
|
if end > len(stringIn):
|
|
end = len(stringIn)
|
|
plaintext = self.convertString(stringIn, start, end, mode)
|
|
# print('PT@%s:%s' % (j, plaintext))
|
|
if mode == self.modeOfOperation["CFB"]:
|
|
if firstRound:
|
|
output = self.aes.encrypt(IV, key, size)
|
|
firstRound = False
|
|
else:
|
|
output = self.aes.encrypt(iput, key, size)
|
|
for i in range(16):
|
|
if len(plaintext)-1 < i:
|
|
ciphertext[i] = 0 ^ output[i]
|
|
elif len(output)-1 < i:
|
|
ciphertext[i] = plaintext[i] ^ 0
|
|
elif len(plaintext)-1 < i and len(output) < i:
|
|
ciphertext[i] = 0 ^ 0
|
|
else:
|
|
ciphertext[i] = plaintext[i] ^ output[i]
|
|
for k in range(end-start):
|
|
cipherOut.append(ciphertext[k])
|
|
iput = ciphertext
|
|
elif mode == self.modeOfOperation["OFB"]:
|
|
if firstRound:
|
|
output = self.aes.encrypt(IV, key, size)
|
|
firstRound = False
|
|
else:
|
|
output = self.aes.encrypt(iput, key, size)
|
|
for i in range(16):
|
|
if len(plaintext)-1 < i:
|
|
ciphertext[i] = 0 ^ output[i]
|
|
elif len(output)-1 < i:
|
|
ciphertext[i] = plaintext[i] ^ 0
|
|
elif len(plaintext)-1 < i and len(output) < i:
|
|
ciphertext[i] = 0 ^ 0
|
|
else:
|
|
ciphertext[i] = plaintext[i] ^ output[i]
|
|
for k in range(end-start):
|
|
cipherOut.append(ciphertext[k])
|
|
iput = output
|
|
elif mode == self.modeOfOperation["CBC"]:
|
|
for i in range(16):
|
|
if firstRound:
|
|
iput[i] = plaintext[i] ^ IV[i]
|
|
else:
|
|
iput[i] = plaintext[i] ^ ciphertext[i]
|
|
# print('IP@%s:%s' % (j, iput))
|
|
firstRound = False
|
|
ciphertext = self.aes.encrypt(iput, key, size)
|
|
# always 16 bytes because of the padding for CBC
|
|
for k in range(16):
|
|
cipherOut.append(ciphertext[k])
|
|
return mode, len(stringIn), cipherOut
|
|
|
|
# Mode of Operation Decryption
|
|
# cipherIn - Encrypted String
|
|
# originalsize - The unencrypted string length - required for CBC
|
|
# mode - mode of type modeOfOperation
|
|
# key - a number array of the bit length size
|
|
# size - the bit length of the key
|
|
# IV - the 128 bit number array Initilization Vector
|
|
def decrypt(self, cipherIn, originalsize, mode, key, size, IV):
|
|
# cipherIn = unescCtrlChars(cipherIn)
|
|
if len(key) % size:
|
|
return None
|
|
if len(IV) % 16:
|
|
return None
|
|
# the AES input/output
|
|
ciphertext = []
|
|
iput = []
|
|
output = []
|
|
plaintext = [0] * 16
|
|
# the output plain text string
|
|
stringOut = b''
|
|
# char firstRound
|
|
firstRound = True
|
|
if cipherIn != None:
|
|
for j in range(int(math.ceil(float(len(cipherIn))//16))):
|
|
start = j*16
|
|
end = j*16+16
|
|
if j*16+16 > len(cipherIn):
|
|
end = len(cipherIn)
|
|
ciphertext = cipherIn[start:end]
|
|
if mode == self.modeOfOperation["CFB"]:
|
|
if firstRound:
|
|
output = self.aes.encrypt(IV, key, size)
|
|
firstRound = False
|
|
else:
|
|
output = self.aes.encrypt(iput, key, size)
|
|
for i in range(16):
|
|
if len(output)-1 < i:
|
|
plaintext[i] = 0 ^ ciphertext[i]
|
|
elif len(ciphertext)-1 < i:
|
|
plaintext[i] = output[i] ^ 0
|
|
elif len(output)-1 < i and len(ciphertext) < i:
|
|
plaintext[i] = 0 ^ 0
|
|
else:
|
|
plaintext[i] = output[i] ^ ciphertext[i]
|
|
for k in range(end-start):
|
|
stringOut += chrsix(plaintext[k])
|
|
iput = ciphertext
|
|
elif mode == self.modeOfOperation["OFB"]:
|
|
if firstRound:
|
|
output = self.aes.encrypt(IV, key, size)
|
|
firstRound = False
|
|
else:
|
|
output = self.aes.encrypt(iput, key, size)
|
|
for i in range(16):
|
|
if len(output)-1 < i:
|
|
plaintext[i] = 0 ^ ciphertext[i]
|
|
elif len(ciphertext)-1 < i:
|
|
plaintext[i] = output[i] ^ 0
|
|
elif len(output)-1 < i and len(ciphertext) < i:
|
|
plaintext[i] = 0 ^ 0
|
|
else:
|
|
plaintext[i] = output[i] ^ ciphertext[i]
|
|
for k in range(end-start):
|
|
stringOut += chrsix(plaintext[k])
|
|
iput = output
|
|
elif mode == self.modeOfOperation["CBC"]:
|
|
output = self.aes.decrypt(ciphertext, key, size)
|
|
for i in range(16):
|
|
if firstRound:
|
|
plaintext[i] = IV[i] ^ output[i]
|
|
else:
|
|
plaintext[i] = iput[i] ^ output[i]
|
|
firstRound = False
|
|
if not(originalsize is None) and originalsize < end:
|
|
for k in range(originalsize-start):
|
|
stringOut += chrsix(plaintext[k])
|
|
else:
|
|
for k in range(end-start):
|
|
stringOut += chrsix(plaintext[k])
|
|
iput = ciphertext
|
|
return stringOut
|
|
|
|
######################
|
|
# end of aes.py code #
|
|
######################
|
|
|
|
###################################
|
|
# pywallet crypter implementation #
|
|
###################################
|
|
|
|
class Crypter_pycrypto( object ):
|
|
def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
|
|
if nDerivationMethod != 0:
|
|
return 0
|
|
data = str_to_bytes(vKeyData) + vSalt
|
|
for i in xrange(nDerivIterations):
|
|
data = hashlib.sha512(data).digest()
|
|
self.SetKey(data[0:32])
|
|
self.SetIV(data[32:32+16])
|
|
return len(data)
|
|
|
|
def SetKey(self, key):
|
|
self.chKey = key
|
|
|
|
def SetIV(self, iv):
|
|
self.chIV = iv[0:16]
|
|
|
|
def Encrypt(self, data):
|
|
return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(append_PKCS7_padding(data))
|
|
|
|
def Decrypt(self, data):
|
|
return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32]
|
|
|
|
class Crypter_ssl(object):
|
|
def __init__(self):
|
|
self.chKey = ctypes.create_string_buffer (32)
|
|
self.chIV = ctypes.create_string_buffer (16)
|
|
|
|
def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
|
|
if nDerivationMethod != 0:
|
|
return 0
|
|
strKeyData = ctypes.create_string_buffer (vKeyData)
|
|
chSalt = ctypes.create_string_buffer (vSalt)
|
|
return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData,
|
|
len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV))
|
|
|
|
def SetKey(self, key):
|
|
self.chKey = ctypes.create_string_buffer(key)
|
|
|
|
def SetIV(self, iv):
|
|
self.chIV = ctypes.create_string_buffer(iv)
|
|
|
|
def Encrypt(self, data):
|
|
buf = ctypes.create_string_buffer(len(data) + 16)
|
|
written = ctypes.c_int(0)
|
|
final = ctypes.c_int(0)
|
|
ctx = ssl.EVP_CIPHER_CTX_new()
|
|
ssl.EVP_CIPHER_CTX_init(ctx)
|
|
ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
|
|
ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
|
|
output = buf.raw[:written.value]
|
|
ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final))
|
|
output += buf.raw[:final.value]
|
|
return output
|
|
|
|
def Decrypt(self, data):
|
|
buf = ctypes.create_string_buffer(len(data) + 16)
|
|
written = ctypes.c_int(0)
|
|
final = ctypes.c_int(0)
|
|
ctx = ssl.EVP_CIPHER_CTX_new()
|
|
ssl.EVP_CIPHER_CTX_init(ctx)
|
|
ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
|
|
ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
|
|
output = buf.raw[:written.value]
|
|
ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final))
|
|
output += buf.raw[:final.value]
|
|
return output
|
|
|
|
class Crypter_pure(object):
|
|
def __init__(self):
|
|
self.m = AESModeOfOperation()
|
|
self.cbc = self.m.modeOfOperation["CBC"]
|
|
self.sz = self.m.aes.keySize["SIZE_256"]
|
|
|
|
def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
|
|
if nDerivationMethod != 0:
|
|
return 0
|
|
data = str_to_bytes(vKeyData) + vSalt
|
|
for i in xrange(nDerivIterations):
|
|
data = hashlib.sha512(data).digest()
|
|
self.SetKey(data[0:32])
|
|
self.SetIV(data[32:32+16])
|
|
return len(data)
|
|
|
|
def SetKey(self, key):
|
|
self.chKey = [ordsix(i) for i in key]
|
|
|
|
def SetIV(self, iv):
|
|
self.chIV = [ordsix(i) for i in iv]
|
|
|
|
def Encrypt(self, data):
|
|
mode, size, cypher = self.m.encrypt(append_PKCS7_padding(data), self.cbc, self.chKey, self.sz, self.chIV)
|
|
return b''.join(map(chrsix, cypher))
|
|
|
|
def Decrypt(self, data):
|
|
chData = [ordsix(i) for i in data]
|
|
return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV)
|
|
|
|
crypter = None
|
|
if crypter is None:
|
|
try:
|
|
from Crypto.Cipher import AES
|
|
crypter = Crypter_pycrypto()
|
|
except:
|
|
try:
|
|
import ctypes
|
|
import ctypes.util
|
|
ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32')
|
|
crypter = Crypter_ssl()
|
|
except:
|
|
crypter = Crypter_pure()
|
|
logging.warning("pycrypto or libssl not found, decryption may be slow")
|
|
|
|
|
|
##########################################
|
|
# end of pywallet crypter implementation #
|
|
##########################################
|
|
|
|
def bytes_to_int(bytes):
|
|
result = 0
|
|
for b in bytes:
|
|
result = result * 256 + ordsix(b)
|
|
return result
|
|
|
|
def int_to_bytes(value, length = None):
|
|
if not length and value == 0:
|
|
result = [0]
|
|
else:
|
|
result = []
|
|
for i in range(0, length or 1+int(math.log(value, 2**8))):
|
|
result.append(value >> (i * 8) & 0xff)
|
|
result.reverse()
|
|
return str(bytearray(result))
|
|
|
|
# secp256k1
|
|
|
|
_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
|
_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
|
_b = 0x0000000000000000000000000000000000000000000000000000000000000007
|
|
_a = 0x0000000000000000000000000000000000000000000000000000000000000000
|
|
_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
|
_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
|
|
|
|
try:
|
|
import ecdsa
|
|
from ecdsa import der
|
|
curve_secp256k1 = ecdsa.ellipticcurve.CurveFp (_p, _a, _b)
|
|
generator_secp256k1 = g = ecdsa.ellipticcurve.Point (curve_secp256k1, _Gx, _Gy, _r)
|
|
randrange = random.SystemRandom().randrange
|
|
secp256k1 = ecdsa.curves.Curve ( "secp256k1", curve_secp256k1, generator_secp256k1, (1, 3, 132, 0, 10) )
|
|
ecdsa.curves.curves.append (secp256k1)
|
|
except:
|
|
missing_dep.append('ecdsa')
|
|
|
|
# python-ecdsa code (EC_KEY implementation)
|
|
|
|
class CurveFp( object ):
|
|
def __init__( self, p, a, b ):
|
|
self.__p = p
|
|
self.__a = a
|
|
self.__b = b
|
|
|
|
def p( self ):
|
|
return self.__p
|
|
|
|
def a( self ):
|
|
return self.__a
|
|
|
|
def b( self ):
|
|
return self.__b
|
|
|
|
def contains_point( self, x, y ):
|
|
return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0
|
|
|
|
def sqrt_root(self, x):
|
|
return pow(x, (self.__p + 1) // 4, self.__p)
|
|
|
|
def y_from_x(self, x, y_odd):
|
|
y = self.sqrt_root(( x * x * x + self.__a * x + self.__b ) % self.__p)
|
|
if (y % 2 == 1) == y_odd:
|
|
return y
|
|
else:
|
|
return self.__p - y
|
|
|
|
class Point( object ):
|
|
def __init__( self, curve, x, y = None, order = None, y_odd = None ):
|
|
self.__curve = curve
|
|
self.__x = x
|
|
if y != None or curve == None:
|
|
self.__y = y
|
|
else:
|
|
self.__y = self.__curve.y_from_x(self.__x, y_odd)
|
|
self.__order = order
|
|
if self.__curve: assert self.__curve.contains_point( self.__x, self.__y )
|
|
if order: assert self * order == INFINITY
|
|
|
|
def __add__( self, other ):
|
|
if other == INFINITY: return self
|
|
if self == INFINITY: return other
|
|
assert self.__curve == other.__curve
|
|
if self.__x == other.__x:
|
|
if ( self.__y + other.__y ) % self.__curve.p() == 0:
|
|
return INFINITY
|
|
else:
|
|
return self.double()
|
|
|
|
p = self.__curve.p()
|
|
l = ( ( other.__y - self.__y ) * \
|
|
inverse_mod( other.__x - self.__x, p ) ) % p
|
|
x3 = ( l * l - self.__x - other.__x ) % p
|
|
y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
|
|
return Point( self.__curve, x3, y3 )
|
|
|
|
def __mul__( self, other ):
|
|
def leftmost_bit( x ):
|
|
assert x > 0
|
|
result = 1
|
|
while result <= x: result = 2 * result
|
|
return result // 2
|
|
|
|
e = other
|
|
if self.__order: e = e % self.__order
|
|
if e == 0: return INFINITY
|
|
if self == INFINITY: return INFINITY
|
|
assert e > 0
|
|
e3 = 3 * e
|
|
negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
|
|
i = leftmost_bit( e3 ) // 2
|
|
result = self
|
|
while i > 1:
|
|
result = result.double()
|
|
if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
|
|
if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
|
|
i = i // 2
|
|
return result
|
|
|
|
def __rmul__( self, other ):
|
|
return self * other
|
|
|
|
def __str__( self ):
|
|
if self == INFINITY: return "infinity"
|
|
return "(%d,%d)" % ( self.__x, self.__y )
|
|
|
|
def double( self ):
|
|
if self == INFINITY:
|
|
return INFINITY
|
|
|
|
p = self.__curve.p()
|
|
a = self.__curve.a()
|
|
l = ( ( 3 * self.__x * self.__x + a ) * \
|
|
inverse_mod( 2 * self.__y, p ) ) % p
|
|
x3 = ( l * l - 2 * self.__x ) % p
|
|
y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
|
|
return Point( self.__curve, x3, y3 )
|
|
|
|
def x( self ):
|
|
return self.__x
|
|
|
|
def y( self ):
|
|
return self.__y
|
|
|
|
def curve( self ):
|
|
return self.__curve
|
|
|
|
def order( self ):
|
|
return self.__order
|
|
|
|
INFINITY = Point( None, None, None )
|
|
secp256k1_curve = CurveFp( _p, _a, _b )
|
|
secp256k1_generator = Point( secp256k1_curve, _Gx, _Gy, _r )
|
|
|
|
def inverse_mod( a, m ):
|
|
if a < 0 or m <= a: a = a % m
|
|
c, d = a, m
|
|
uc, vc, ud, vd = 1, 0, 0, 1
|
|
while c != 0:
|
|
q, c, d = divmod( d, c ) + ( c, )
|
|
uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
|
|
assert d == 1
|
|
if ud > 0: return ud
|
|
else: return ud + m
|
|
|
|
class Signature( object ):
|
|
def __init__( self, r, s ):
|
|
self.r = r
|
|
self.s = s
|
|
|
|
class Public_key( object ):
|
|
def __init__( self, generator, point, c=None ):
|
|
self.curve = generator.curve()
|
|
self.generator = generator
|
|
self.point = point
|
|
self.compressed = c
|
|
n = generator.order()
|
|
if not n:
|
|
raise RuntimeError("Generator point must have order.")
|
|
if not n * point == INFINITY:
|
|
raise RuntimeError("Generator point order is bad.")
|
|
if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y():
|
|
raise RuntimeError("Generator point has x or y out of range.")
|
|
|
|
def verifies( self, hash, signature ):
|
|
G = self.generator
|
|
n = G.order()
|
|
r = signature.r
|
|
s = signature.s
|
|
if r < 1 or r > n-1: return False
|
|
if s < 1 or s > n-1: return False
|
|
c = inverse_mod( s, n )
|
|
u1 = ( hash * c ) % n
|
|
u2 = ( r * c ) % n
|
|
xy = u1 * G + u2 * self.point
|
|
v = xy.x() % n
|
|
return v == r
|
|
|
|
def ser(self):
|
|
if self.compressed:
|
|
pk=('%02x'%(2+(self.point.y()&1))) + '%064x' % self.point.x()
|
|
else:
|
|
pk='04%064x%064x' % (self.point.x(), self.point.y())
|
|
|
|
return binascii.unhexlify(pk)
|
|
|
|
def get_addr(self, v=0):
|
|
return public_key_to_bc_address(self.ser(), v)
|
|
|
|
@classmethod
|
|
def from_ser(cls, g, ser):
|
|
if len(ser) == 33:
|
|
return cls(g, Point(g.curve(), bytes_to_int(ser[1:]), y_odd = ordsix(ser[0]) == 3), ordsix(ser[0]) < 4)
|
|
elif len(ser) == 65:
|
|
return cls(g, Point(g.curve(), bytes_to_int(ser[1:33]), bytes_to_int(ser[33:])), ordsix(ser[0]) < 4)
|
|
raise Exception("Bad public key format: %s"%repr(ser))
|
|
|
|
class Private_key( object ):
|
|
def __init__( self, public_key, secret_multiplier ):
|
|
self.public_key = public_key
|
|
self.secret_multiplier = secret_multiplier
|
|
|
|
def der( self ):
|
|
hex_der_key = '06052b8104000a30740201010420' + \
|
|
'%064x' % self.secret_multiplier + \
|
|
'a00706052b8104000aa14403420004' + \
|
|
'%064x' % self.public_key.point.x() + \
|
|
'%064x' % self.public_key.point.y()
|
|
return binascii.unhexlify(hex_der_key)
|
|
|
|
def sign( self, hash, random_k ):
|
|
G = self.public_key.generator
|
|
n = G.order()
|
|
k = random_k % n
|
|
p1 = k * G
|
|
r = p1.x()
|
|
if r == 0: raise RuntimeError("amazingly unlucky random number r")
|
|
s = ( inverse_mod( k, n ) * \
|
|
( hash + ( self.secret_multiplier * r ) % n ) ) % n
|
|
if s == 0: raise RuntimeError("amazingly unlucky random number s")
|
|
return Signature( r, s )
|
|
|
|
class EC_KEY(object):
|
|
def __init__( self, secret ):
|
|
curve = CurveFp( _p, _a, _b )
|
|
generator = Point( curve, _Gx, _Gy, _r )
|
|
self.pubkey = Public_key( generator, generator * secret )
|
|
self.privkey = Private_key( self.pubkey, secret )
|
|
self.secret = secret
|
|
|
|
# end of python-ecdsa code
|
|
|
|
# pywallet openssl private key implementation
|
|
|
|
def i2d_ECPrivateKey(pkey, compressed=False):#, crypted=True):
|
|
part3='a081a53081a2020101302c06072a8648ce3d0101022100' # for uncompressed keys
|
|
if compressed:
|
|
if True:#not crypted: ## Bitcoin accepts both part3's for crypted wallets...
|
|
part3='a08185308182020101302c06072a8648ce3d0101022100' # for compressed keys
|
|
key = '3081d30201010420' + \
|
|
'%064x' % pkey.secret + \
|
|
part3 + \
|
|
'%064x' % _p + \
|
|
'3006040100040107042102' + \
|
|
'%064x' % _Gx + \
|
|
'022100' + \
|
|
'%064x' % _r + \
|
|
'020101a124032200'
|
|
else:
|
|
key = '308201130201010420' + \
|
|
'%064x' % pkey.secret + \
|
|
part3 + \
|
|
'%064x' % _p + \
|
|
'3006040100040107044104' + \
|
|
'%064x' % _Gx + \
|
|
'%064x' % _Gy + \
|
|
'022100' + \
|
|
'%064x' % _r + \
|
|
'020101a144034200'
|
|
|
|
return binascii.unhexlify(key) + i2o_ECPublicKey(pkey, compressed)
|
|
|
|
def i2o_ECPublicKey(pkey, compressed=False):
|
|
# public keys are 65 bytes long (520 bits)
|
|
# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
|
|
# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
|
|
# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
|
|
if compressed:
|
|
if pkey.pubkey.point.y() & 1:
|
|
key = '03' + '%064x' % pkey.pubkey.point.x()
|
|
else:
|
|
key = '02' + '%064x' % pkey.pubkey.point.x()
|
|
else:
|
|
key = '04' + \
|
|
'%064x' % pkey.pubkey.point.x() + \
|
|
'%064x' % pkey.pubkey.point.y()
|
|
|
|
return binascii.unhexlify(key)
|
|
|
|
# bitcointools hashes and base58 implementation
|
|
|
|
def hash_160(public_key):
|
|
md = hashlib.new('ripemd160')
|
|
md.update(hashlib.sha256(public_key).digest())
|
|
return md.digest()
|
|
|
|
def public_key_to_bc_address(public_key, v=None):
|
|
if v==None:
|
|
v=network.p2pkh_prefix
|
|
h160 = hash_160(public_key)
|
|
return hash_160_to_bc_address(h160, v)
|
|
|
|
def hash_160_to_bc_address(h160, v=None):
|
|
if v==None:
|
|
v=network.p2pkh_prefix
|
|
vh160 = chrsix(v) + h160
|
|
h = Hash(vh160)
|
|
addr = vh160 + h[0:4]
|
|
return b58encode(addr)
|
|
|
|
def bc_address_to_hash_160(addr):
|
|
bytes = b58decode(addr, 25)
|
|
return bytes[1:21]
|
|
|
|
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
def b58encode(v, __b58chars=__b58chars):
|
|
""" encode v, which is a string of bytes, to base58.
|
|
"""
|
|
__b58base = len(__b58chars)
|
|
|
|
long_value = 0
|
|
for (i, c) in enumerate(v[::-1]):
|
|
long_value += (256**i) * ordsix(c)
|
|
|
|
result = ''
|
|
while long_value >= __b58base:
|
|
div, mod = divmod(long_value, __b58base)
|
|
result = __b58chars[mod] + result
|
|
long_value = div
|
|
result = __b58chars[long_value] + result
|
|
|
|
# Bitcoin does a little leading-zero-compression:
|
|
# leading 0-bytes in the input become leading-1s
|
|
nPad = 0
|
|
for c in v:
|
|
if chrsix(c) == b'\0': nPad += 1
|
|
else: break
|
|
|
|
return (__b58chars[0]*nPad) + result
|
|
|
|
def b58decode(v, length, __b58chars=__b58chars):
|
|
""" decode v into a string of len bytes
|
|
"""
|
|
__b58base = len(__b58chars)
|
|
long_value = 0
|
|
for (i, c) in enumerate(v[::-1]):
|
|
long_value += __b58chars.find(c) * (__b58base**i)
|
|
|
|
result = b''
|
|
while long_value >= 256:
|
|
div, mod = divmod(long_value, 256)
|
|
result = chrsix(mod) + result
|
|
long_value = div
|
|
result = chrsix(long_value) + result
|
|
|
|
nPad = 0
|
|
for c in v:
|
|
if c == __b58chars[0]: nPad += 1
|
|
else: break
|
|
|
|
result = chrsix(0)*nPad + result
|
|
if not(length is None) and len(result) != length:
|
|
return None
|
|
|
|
return result
|
|
|
|
# end of bitcointools base58 implementation
|
|
|
|
# address handling code
|
|
|
|
def Hash(data):
|
|
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
|
|
|
def EncodeBase58Check(secret, __b58chars=__b58chars):
|
|
hash = Hash(secret)
|
|
return b58encode(secret + hash[0:4], __b58chars)
|
|
|
|
def DecodeBase58Check(sec, __b58chars=__b58chars):
|
|
vchRet = b58decode(sec, None, __b58chars)
|
|
secret = vchRet[0:-4]
|
|
csum = vchRet[-4:]
|
|
hash = Hash(secret)
|
|
cs32 = hash[0:4]
|
|
if cs32 != csum:
|
|
return None
|
|
else:
|
|
return secret
|
|
|
|
def str_to_long(b):
|
|
res = 0
|
|
pos = 1
|
|
for a in reversed(b):
|
|
res += ordsix(a) * pos
|
|
pos *= 256
|
|
return res
|
|
|
|
def PrivKeyToSecret(privkey):
|
|
if len(privkey) == 279:
|
|
return privkey[9:9+32]
|
|
else:
|
|
return privkey[8:8+32]
|
|
|
|
def SecretToASecret(secret, compressed=False):
|
|
prefix = chrsix(network.wif_prefix)
|
|
vchIn = prefix + secret
|
|
if compressed: vchIn += b'\01'
|
|
return EncodeBase58Check(vchIn)
|
|
|
|
def ASecretToSecret(sec):
|
|
vch = DecodeBase58Check(sec)
|
|
if not vch:
|
|
return False
|
|
if ordsix(vch[0]) != network.wif_prefix:
|
|
print('Warning: adress prefix seems bad (%d vs %d)'%(ordsix(vch[0]), network.wif_prefix))
|
|
return vch[1:]
|
|
|
|
def regenerate_key(sec):
|
|
b = ASecretToSecret(sec)
|
|
if not b:
|
|
return False
|
|
b = b[0:32]
|
|
secret = int(b'0x' + binascii.hexlify(b), 16)
|
|
return EC_KEY(secret)
|
|
|
|
def GetPubKey(pkey, compressed=False):
|
|
return i2o_ECPublicKey(pkey, compressed)
|
|
|
|
def GetPrivKey(pkey, compressed=False):
|
|
return i2d_ECPrivateKey(pkey, compressed)
|
|
|
|
def GetSecret(pkey):
|
|
return binascii.unhexlify('%064x' % pkey.secret)
|
|
|
|
def is_compressed(sec):
|
|
b = ASecretToSecret(sec)
|
|
return len(b) == 33
|
|
|
|
# bitcointools wallet.dat handling code
|
|
|
|
def create_env(db_dir):
|
|
db_env = DBEnv(0)
|
|
r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER))
|
|
return db_env
|
|
|
|
def parse_CAddress(vds):
|
|
d = Bdict({'ip':'0.0.0.0','port':0,'nTime': 0})
|
|
try:
|
|
d['nVersion'] = vds.read_int32()
|
|
d['nTime'] = vds.read_uint32()
|
|
d['nServices'] = vds.read_uint64()
|
|
d['pchReserved'] = vds.read_bytes(12)
|
|
d['ip'] = socket.inet_ntoa(vds.read_bytes(4))
|
|
d['port'] = vds.read_uint16()
|
|
except:
|
|
pass
|
|
return d
|
|
|
|
def deserialize_CAddress(d):
|
|
return d['ip']+":"+str(d['port'])
|
|
|
|
def parse_BlockLocator(vds):
|
|
d = Bdict({ 'hashes' : [] })
|
|
nHashes = vds.read_compact_size()
|
|
for i in xrange(nHashes):
|
|
d['hashes'].append(vds.read_bytes(32))
|
|
return d
|
|
|
|
def deserialize_BlockLocator(d):
|
|
result = "Block Locator top: " + binascii.hexlify(d['hashes'][0][::-1])
|
|
return result
|
|
|
|
def parse_setting(setting, vds):
|
|
if setting[0] == "f": # flag (boolean) settings
|
|
return str(vds.read_boolean())
|
|
elif setting[0:4] == "addr": # CAddress
|
|
d = parse_CAddress(vds)
|
|
return deserialize_CAddress(d)
|
|
elif setting == "nTransactionFee":
|
|
return vds.read_int64()
|
|
elif setting == "nLimitProcessors":
|
|
return vds.read_int32()
|
|
return 'unknown setting'
|
|
|
|
class SerializationError(Exception):
|
|
""" Thrown when there's a problem deserializing or serializing """
|
|
|
|
def overlapped_read(f, sz, overlap, maxlen=None):
|
|
buffer = b''
|
|
stop = False
|
|
total_read = 0
|
|
while not stop and (not maxlen or maxlen > total_read):
|
|
new_content = os.read(f, sz)
|
|
if not new_content:break
|
|
total_read += len(new_content)
|
|
buffer = buffer[-overlap:] + new_content
|
|
yield buffer
|
|
|
|
def search_patterns_on_disk(device, size, inc, patternlist): # inc must be higher than 1k
|
|
try:
|
|
otype=os.O_RDONLY|os.O_BINARY
|
|
except:
|
|
otype=os.O_RDONLY
|
|
try:
|
|
fd = os.open(device, otype)
|
|
except Exception as e:
|
|
print("Can't open %s, check the path or try as root"%device)
|
|
print(" Error: "+str(e.args))
|
|
exit(0)
|
|
|
|
i = 0
|
|
data=b''
|
|
|
|
tzero=time.time()
|
|
sizetokeep=0
|
|
BlocksToInspect=dict(map(lambda x:[x,[]], patternlist))
|
|
lendataloaded=None
|
|
writeProgressEvery=100*Mo
|
|
while i < int(size) and (lendataloaded!=0 or lendataloaded==None):
|
|
if int(i//writeProgressEvery)!=int((i+inc)//writeProgressEvery):
|
|
print("%.2f Go read"%(i//1e9))
|
|
try:
|
|
datakept = data[-sizetokeep:]
|
|
data = datakept + os.read(fd, inc)
|
|
lendataloaded = len(data)-len(datakept) #should be inc
|
|
for text in patternlist:
|
|
if text in data:
|
|
BlocksToInspect[text].append([i-len(datakept), data, len(datakept)])
|
|
pass
|
|
sizetokeep=20 # 20 because all the patterns have a len<20. Could be higher.
|
|
i += lendataloaded
|
|
except Exception as exc:
|
|
if lendataloaded%512>0:
|
|
raise Exception("SPOD error 1: %d, %d"%(lendataloaded, i-len(datakept)))
|
|
os.lseek(fd, lendataloaded, os.SEEK_CUR)
|
|
print(str(exc))
|
|
i += lendataloaded
|
|
continue
|
|
os.close(fd)
|
|
|
|
AllOffsets=dict(map(lambda x:[repr(x),[]], patternlist))
|
|
for text,blocks in BlocksToInspect.items():
|
|
for offset,data,ldk in blocks: #ldk = len(datakept)
|
|
offsetslist=[offset+m.start() for m in re.finditer(text, data)]
|
|
AllOffsets[repr(text)].extend(offsetslist)
|
|
|
|
AllOffsets['PRFdevice']=device
|
|
AllOffsets['PRFdt']=time.time()-tzero
|
|
AllOffsets['PRFsize']=i
|
|
return AllOffsets
|
|
|
|
def multiextract(s, ll):
|
|
r=[]
|
|
cursor=0
|
|
for length in ll:
|
|
r.append(s[cursor:cursor+length])
|
|
cursor+=length
|
|
if s[cursor:]!=b'':
|
|
r.append(s[cursor:])
|
|
return r
|
|
|
|
class RecovCkey(object):
|
|
def __init__(self, epk, pk):
|
|
self.encrypted_pk=epk
|
|
self.public_key=pk
|
|
self.mkey=None
|
|
self.privkey=None
|
|
|
|
|
|
class RecovMkey(object):
|
|
def __init__(self, ekey, salt, nditer, ndmethod, nid):
|
|
self.encrypted_key=ekey
|
|
self.salt=salt
|
|
self.iterations=nditer
|
|
self.method=ndmethod
|
|
self.id=nid
|
|
# print((ekey, salt, nditer, ndmethod, nid))
|
|
|
|
def readpartfile(fd, offset, length): #make everything 512*n because of windows...
|
|
rest=offset%512
|
|
new_offset=offset-rest
|
|
big_length=512*(int((length+rest-1)//512)+1)
|
|
os.lseek(fd, new_offset, os.SEEK_SET)
|
|
d=os.read(fd, big_length)
|
|
return d[rest:rest+length]
|
|
|
|
def recov_ckey(fd, offset):
|
|
d=readpartfile(fd, offset-49, 122)
|
|
me=multiextract(d, [1,48,4,4,1])
|
|
|
|
checks=[]
|
|
checks.append([0, '30'])
|
|
checks.append([3, '636b6579'])
|
|
if sum(map(lambda x:int(me[x[0]] != binascii.unhexlify(x[1])), checks)): #number of false statements
|
|
return None
|
|
|
|
return me
|
|
|
|
def recov_mkey(fd, offset):
|
|
d=readpartfile(fd, offset-72, 84)
|
|
me=multiextract(d, [4,48,1,8,4,4,1,2,8,4])
|
|
|
|
checks=[]
|
|
checks.append([0, '43000130'])
|
|
checks.append([2, '08'])
|
|
checks.append([6, '00'])
|
|
checks.append([8, '090001046d6b6579'])
|
|
if sum(map(lambda x:int(me[x[0]] != binascii.unhexlify(x[1])), checks)): #number of false statements
|
|
return None
|
|
|
|
return me
|
|
|
|
def drop_first(e):
|
|
if hasattr(e, 'next'):
|
|
e.next()
|
|
else:
|
|
e=e[1:]
|
|
for i in e:yield i
|
|
|
|
def recov_uckey(fd, offset):
|
|
dd = readpartfile(fd, offset, 223)
|
|
r = []
|
|
for beg in map(binascii.unhexlify, ['3081d30201010420', '308201130201010420']):
|
|
for chunk in drop_first(dd.split(beg)):
|
|
r.append(chunk[:32])
|
|
return r and (None, None, None, None, r[0])
|
|
|
|
def recov_uckeyOLD(fd, offset):
|
|
checks=[]
|
|
|
|
d=readpartfile(fd, offset-217, 223)
|
|
if d[-7]=='\x26':
|
|
me=multiextract(d, [2,1,4,1,32,141,33,2,1,6])
|
|
|
|
checks.append([0, '3081'])
|
|
checks.append([2, '02010104'])
|
|
elif d[-7]=='\x46':
|
|
d=readpartfile(fd, offset-282, 286)
|
|
|
|
me=multiextract(d, [2,1,4,1,32,173,65,1,2,5])
|
|
|
|
checks.append([0, '8201'])
|
|
checks.append([2, '02010104'])
|
|
checks.append([-1, '460001036b'])
|
|
else:
|
|
return None
|
|
|
|
|
|
if sum(map(lambda x:int(me[x[0]] != binascii.unhexlify(x[1])), checks)): #number of false statements
|
|
return None
|
|
|
|
return me
|
|
|
|
def recov(device, passes, size=102400, inc=10240, outputdir='.'):
|
|
if inc%512>0:
|
|
inc-=inc%512 #inc must be 512*n on Windows... Don't ask me why...
|
|
|
|
nameToDBName={
|
|
'mkey':b'\x09\x00\x01\x04mkey',
|
|
'ckey':b'\x27\x00\x01\x04ckey',
|
|
'key':b'\x00\x01\x03key'
|
|
}
|
|
|
|
|
|
if not device.startswith('PartialRecoveryFile:'):
|
|
r=search_patterns_on_disk(device, size, inc, nameToDBName.values())
|
|
f=open(outputdir+'/pywallet_partial_recovery_%d.json'%ts(), 'w')
|
|
f.write(json.dumps(r))
|
|
f.close()
|
|
print("\nRead %.1f Go in %.1f minutes\n"%(r['PRFsize']//1e9, r['PRFdt']//60.0))
|
|
else:
|
|
prf=device[20:]
|
|
f=open(prf, 'r')
|
|
content = f.read()
|
|
f.close()
|
|
r=json.loads(content)
|
|
device=r['PRFdevice']
|
|
print("\nLoaded %.1f Go from %s\n"%(r['PRFsize']//1e9, device))
|
|
|
|
|
|
try:
|
|
otype=os.O_RDONLY|os.O_BINARY
|
|
except:
|
|
otype=os.O_RDONLY
|
|
fd = os.open(device, otype)
|
|
|
|
|
|
mkeys=[]
|
|
crypters=[]
|
|
for offset in r[repr(nameToDBName['mkey'])]:
|
|
s=recov_mkey(fd, offset)
|
|
if s==None:
|
|
continue
|
|
if s[-1] == b'':s=s[:-1]
|
|
newmkey=RecovMkey(
|
|
s[1],
|
|
s[3],
|
|
int(binascii.hexlify(s[5][::-1]), 16),
|
|
int(binascii.hexlify(s[4][::-1]), 16),
|
|
int(binascii.hexlify(s[-1][::-1]), 16)
|
|
)
|
|
mkeys.append([offset,newmkey])
|
|
|
|
print("Found %d possible wallets"%len(mkeys))
|
|
|
|
|
|
|
|
|
|
ckeys=[]
|
|
for offset in r[repr(nameToDBName['ckey'])]:
|
|
s=recov_ckey(fd, offset)
|
|
if s==None:
|
|
continue
|
|
newckey=RecovCkey(s[1], s[5][:int(binascii.hexlify(s[4]),16)])
|
|
ckeys.append([offset,newckey])
|
|
print('Found %d possible encrypted keys'%len(ckeys))
|
|
|
|
|
|
uckeys=[]
|
|
for offset in r[repr(nameToDBName['key'])]:
|
|
s=recov_uckey(fd, offset)
|
|
if s:
|
|
uckeys.append(s[4])
|
|
uckeys = list(set(uckeys))
|
|
print('Found %d possible unencrypted keys'%len(uckeys))
|
|
|
|
|
|
os.close(fd)
|
|
|
|
|
|
list_of_possible_keys_per_master_key=dict(map(lambda x:[x[1],[]], mkeys))
|
|
for cko,ck in ckeys:
|
|
tl=map(lambda x:[abs(x[0]-cko)]+x, mkeys)
|
|
tl=sorted(tl, key=lambda x:x[0])
|
|
list_of_possible_keys_per_master_key[tl[0][2]].append(ck)
|
|
|
|
cpt=0
|
|
mki=1
|
|
tzero=time.time()
|
|
if len(passes)==0:
|
|
if len(ckeys)>0:
|
|
print("Can't decrypt them as you didn't provide any passphrase.")
|
|
else:
|
|
for mko,mk in mkeys:
|
|
list_of_possible_keys=list_of_possible_keys_per_master_key[mk]
|
|
sys.stdout.write( "\nPossible wallet #"+str(mki))
|
|
sys.stdout.flush()
|
|
for ppi,pp in enumerate(passes):
|
|
sys.stdout.write( "\n with passphrase #"+str(ppi+1)+" ")
|
|
sys.stdout.flush()
|
|
failures_in_a_row=0
|
|
# print("SKFP params:", pp, mk.salt, mk.iterations, mk.method)
|
|
res = crypter.SetKeyFromPassphrase(pp, mk.salt, mk.iterations, mk.method)
|
|
if res == 0:
|
|
print("Unsupported derivation method")
|
|
sys.exit(1)
|
|
masterkey = crypter.Decrypt(mk.encrypted_key)
|
|
crypter.SetKey(masterkey)
|
|
for ck in list_of_possible_keys:
|
|
if cpt%10==9 and failures_in_a_row==0:
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
if failures_in_a_row>5:
|
|
break
|
|
crypter.SetIV(Hash(ck.public_key))
|
|
secret = crypter.Decrypt(ck.encrypted_pk)
|
|
compressed = ck.public_key[0] != '\04'
|
|
|
|
|
|
pkey = EC_KEY(int(b'0x' + binascii.hexlify(secret), 16))
|
|
if ck.public_key != GetPubKey(pkey, compressed):
|
|
failures_in_a_row+=1
|
|
else:
|
|
failures_in_a_row=0
|
|
ck.mkey=mk
|
|
ck.privkey=secret
|
|
cpt+=1
|
|
mki+=1
|
|
print("\n")
|
|
tone=time.time()
|
|
try:
|
|
calcspeed=1.0*cpt//(tone-tzero)*60 #calc/min
|
|
except:
|
|
calcspeed=1.0
|
|
if calcspeed==0:
|
|
calcspeed=1.0
|
|
|
|
ckeys_not_decrypted=list(filter(lambda x:x[1].privkey==None, ckeys))
|
|
refused_to_test_all_pps=True
|
|
if len(ckeys_not_decrypted)==0:
|
|
print("All the found encrypted private keys have been decrypted.")
|
|
return map(lambda x:x[1].privkey, ckeys)
|
|
else:
|
|
print("Private keys not decrypted: %d"%len(ckeys_not_decrypted))
|
|
print("Trying all the remaining possibilities (%d) might take up to %d minutes."%(len(ckeys_not_decrypted)*len(passes)*len(mkeys),int(len(ckeys_not_decrypted)*len(passes)*len(mkeys)//calcspeed)))
|
|
cont=raw_input("Do you want to test them? (y/n): ")
|
|
while len(cont)==0:
|
|
cont=raw_input("Do you want to test them? (y/n): ")
|
|
if cont[0]=='y':
|
|
refused_to_test_all_pps=False
|
|
cpt=0
|
|
for dist,mko,mk in tl:
|
|
for ppi,pp in enumerate(passes):
|
|
res = crypter.SetKeyFromPassphrase(pp, mk.salt, mk.iterations, mk.method)
|
|
if res == 0:
|
|
logging.error("Unsupported derivation method")
|
|
sys.exit(1)
|
|
masterkey = crypter.Decrypt(mk.encrypted_key)
|
|
crypter.SetKey(masterkey)
|
|
for cko,ck in ckeys_not_decrypted:
|
|
tl=map(lambda x:[abs(x[0]-cko)]+x, mkeys)
|
|
tl=sorted(tl, key=lambda x:x[0])
|
|
if mk==tl[0][2]:
|
|
continue #because already tested
|
|
crypter.SetIV(Hash(ck.public_key))
|
|
secret = crypter.Decrypt(ck.encrypted_pk)
|
|
compressed = ck.public_key[0] != '\04'
|
|
|
|
|
|
pkey = EC_KEY(int(b'0x' + binascii.hexlify(secret), 16))
|
|
if ck.public_key == GetPubKey(pkey, compressed):
|
|
ck.mkey=mk
|
|
ck.privkey=secret
|
|
cpt+=1
|
|
|
|
print("")
|
|
ckeys_not_decrypted=filter(lambda x:x[1].privkey==None, ckeys)
|
|
if len(ckeys_not_decrypted)==0:
|
|
print("All the found encrypted private keys have been finally decrypted.")
|
|
elif not refused_to_test_all_pps:
|
|
print("Private keys not decrypted: %d"%len(ckeys_not_decrypted))
|
|
print("Try another password, check the size of your partition or seek help")
|
|
|
|
|
|
uncrypted_ckeys=filter(lambda x:x!=None, map(lambda x:x[1].privkey, ckeys))
|
|
uckeys.extend(uncrypted_ckeys)
|
|
|
|
return uckeys
|
|
|
|
|
|
|
|
|
|
def ts():
|
|
return int(time.mktime(datetime.now().timetuple()))
|
|
|
|
def check_postkeys(key, postkeys):
|
|
for i in postkeys:
|
|
if key[:len(i)] == i:
|
|
return True
|
|
return False
|
|
|
|
def one_element_in(a, string):
|
|
for i in a:
|
|
if i in string:
|
|
return True
|
|
return False
|
|
|
|
def first_read(device, size, prekeys, inc=10000):
|
|
t0 = ts()-1
|
|
try:
|
|
fd = os.open (device, os.O_RDONLY)
|
|
except:
|
|
print("Can't open %s, check the path or try as root"%device)
|
|
exit(0)
|
|
prekey = prekeys[0]
|
|
data = b""
|
|
i = 0
|
|
data = os.read (fd, i)
|
|
before_contained_key = False
|
|
contains_key = False
|
|
ranges = []
|
|
|
|
while i < int(size):
|
|
if i%(10*Mio) > 0 and i%(10*Mio) <= inc:
|
|
print("\n%.2f/%.2f Go"%(i//1e9, size//1e9))
|
|
t = ts()
|
|
speed = i//(t-t0)
|
|
ETAts = size//speed + t0
|
|
d = datetime.fromtimestamp(ETAts)
|
|
print(d.strftime(" ETA: %H:%M:%S"))
|
|
|
|
try:
|
|
data = os.read (fd, inc)
|
|
except Exception as exc:
|
|
os.lseek(fd, inc, os.SEEK_CUR)
|
|
print(str(exc))
|
|
i += inc
|
|
continue
|
|
|
|
contains_key = one_element_in(prekeys, data)
|
|
|
|
if not before_contained_key and contains_key:
|
|
ranges.append(i)
|
|
|
|
if before_contained_key and not contains_key:
|
|
ranges.append(i)
|
|
|
|
before_contained_key = contains_key
|
|
|
|
i += inc
|
|
|
|
os.close (fd)
|
|
return ranges
|
|
|
|
def shrink_intervals(device, ranges, prekeys, inc=1000):
|
|
prekey = prekeys[0]
|
|
nranges = []
|
|
fd = os.open (device, os.O_RDONLY)
|
|
for j in range(len(ranges)//2):
|
|
before_contained_key = False
|
|
contains_key = False
|
|
bi = ranges[2*j]
|
|
bf = ranges[2*j+1]
|
|
|
|
mini_blocks = []
|
|
k = bi
|
|
while k <= bf + len(prekey) + 1:
|
|
mini_blocks.append(k)
|
|
k += inc
|
|
mini_blocks.append(k)
|
|
|
|
for k in range(len(mini_blocks)//2):
|
|
mini_blocks[2*k] -= len(prekey) +1
|
|
mini_blocks[2*k+1] += len(prekey) +1
|
|
|
|
|
|
bi = mini_blocks[2*k]
|
|
bf = mini_blocks[2*k+1]
|
|
|
|
os.lseek(fd, bi, 0)
|
|
|
|
data = os.read(fd, bf-bi+1)
|
|
contains_key = one_element_in(prekeys, data)
|
|
|
|
if not before_contained_key and contains_key:
|
|
nranges.append(bi)
|
|
|
|
if before_contained_key and not contains_key:
|
|
nranges.append(bi+len(prekey) +1+len(prekey) +1)
|
|
|
|
before_contained_key = contains_key
|
|
|
|
os.close (fd)
|
|
|
|
return nranges
|
|
|
|
def find_offsets(device, ranges, prekeys):
|
|
prekey = prekeys[0]
|
|
list_offsets = []
|
|
to_read = 0
|
|
fd = os.open (device, os.O_RDONLY)
|
|
for i in range(len(ranges)//2):
|
|
bi = ranges[2*i]-len(prekey)-1
|
|
os.lseek(fd, bi, 0)
|
|
bf = ranges[2*i+1]+len(prekey)+1
|
|
to_read += bf-bi+1
|
|
buf = b""
|
|
for j in range(len(prekey)):
|
|
buf += b"\x00"
|
|
curs = bi
|
|
|
|
while curs <= bf:
|
|
data = os.read(fd, 1)
|
|
buf = buf[1:] + data
|
|
if buf in prekeys:
|
|
list_offsets.append(curs)
|
|
curs += 1
|
|
|
|
os.close (fd)
|
|
|
|
return [to_read, list_offsets]
|
|
|
|
def read_keys(device, list_offsets):
|
|
found_hexkeys = []
|
|
fd = os.open (device, os.O_RDONLY)
|
|
for offset in list_offsets:
|
|
os.lseek(fd, offset+1, 0)
|
|
data = os.read(fd, 40)
|
|
hexkey = binascii.hexlify(data[1:33])
|
|
after_key = binascii.hexlify(data[33:39])
|
|
if hexkey not in found_hexkeys and check_postkeys(binascii.unhexlify(after_key), postkeys):
|
|
found_hexkeys.append(hexkey)
|
|
|
|
os.close (fd)
|
|
|
|
return found_hexkeys
|
|
|
|
def read_device_size(size):
|
|
n, prefix, bi = re.match(r'(\d+)(|k|M|G|T|P)(i?)[oB]?$', size).groups()
|
|
r = int(int(n) * pow(1000+int(bool(bi))*24, 'zkMGTP'.index(prefix or 'z')))
|
|
return r
|
|
|
|
def md5_2(a):
|
|
return hashlib.md5(a).digest()
|
|
|
|
def md5_file(nf):
|
|
try:
|
|
fichier = file(nf, 'r').read()
|
|
return md5_2(fichier)
|
|
except:
|
|
return 'zz'
|
|
|
|
def md5_onlinefile(add):
|
|
page = urllib.urlopen(add).read()
|
|
return md5_2(page)
|
|
|
|
|
|
class KEY:
|
|
def __init__ (self):
|
|
self.prikey = None
|
|
self.pubkey = None
|
|
|
|
def generate (self, secret=None):
|
|
if secret:
|
|
exp = int (b'0x' + binascii.hexlify(secret), 16)
|
|
self.prikey = ecdsa.SigningKey.from_secret_exponent (exp, curve=secp256k1)
|
|
else:
|
|
self.prikey = ecdsa.SigningKey.generate (curve=secp256k1)
|
|
self.pubkey = self.prikey.get_verifying_key()
|
|
return self.prikey.to_der()
|
|
|
|
def set_privkey (self, key):
|
|
if len(key) == 279:
|
|
seq1, rest = der.remove_sequence (key)
|
|
integer, rest = der.remove_integer (seq1)
|
|
octet_str, rest = der.remove_octet_string (rest)
|
|
tag1, cons1, rest, = der.remove_constructed (rest)
|
|
tag2, cons2, rest, = der.remove_constructed (rest)
|
|
point_str, rest = der.remove_bitstring (cons2)
|
|
self.prikey = ecdsa.SigningKey.from_string(octet_str, curve=secp256k1)
|
|
else:
|
|
self.prikey = ecdsa.SigningKey.from_der (key)
|
|
|
|
def set_pubkey (self, key):
|
|
key = key[1:]
|
|
self.pubkey = ecdsa.VerifyingKey.from_string (key, curve=secp256k1)
|
|
|
|
def get_privkey (self):
|
|
_p = self.prikey.curve.curve.p ()
|
|
_r = self.prikey.curve.generator.order ()
|
|
_Gx = self.prikey.curve.generator.x ()
|
|
_Gy = self.prikey.curve.generator.y ()
|
|
encoded_oid2 = der.encode_oid (*(1, 2, 840, 10045, 1, 1))
|
|
encoded_gxgy = binascii.unhexlify("04" + ("%64x" % _Gx) + ("%64x" % _Gy))
|
|
param_sequence = der.encode_sequence (
|
|
ecdsa.der.encode_integer(1),
|
|
der.encode_sequence (
|
|
encoded_oid2,
|
|
der.encode_integer (_p),
|
|
),
|
|
der.encode_sequence (
|
|
der.encode_octet_string("\x00"),
|
|
der.encode_octet_string("\x07"),
|
|
),
|
|
der.encode_octet_string (encoded_gxgy),
|
|
der.encode_integer (_r),
|
|
der.encode_integer (1),
|
|
);
|
|
encoded_vk = "\x00\x04" + self.pubkey.to_string ()
|
|
return der.encode_sequence (
|
|
der.encode_integer (1),
|
|
der.encode_octet_string (self.prikey.to_string ()),
|
|
der.encode_constructed (0, param_sequence),
|
|
der.encode_constructed (1, der.encode_bitstring (encoded_vk)),
|
|
)
|
|
|
|
def get_pubkey (self):
|
|
return "\x04" + self.pubkey.to_string()
|
|
|
|
def sign (self, hash):
|
|
sig = self.prikey.sign_digest (hash, sigencode=ecdsa.util.sigencode_der)
|
|
return binascii.hexlify(sig)
|
|
|
|
def verify (self, hash, sig):
|
|
return self.pubkey.verify_digest (sig, hash, sigdecode=ecdsa.util.sigdecode_der)
|
|
|
|
class BCDataStream(object):
|
|
def __init__(self):
|
|
self.input = None
|
|
self.read_cursor = 0
|
|
|
|
def clear(self):
|
|
self.input = None
|
|
self.read_cursor = 0
|
|
|
|
def write(self, bytes): # Initialize with string of bytes
|
|
if self.input is None:
|
|
self.input = bytes
|
|
else:
|
|
self.input += bytes
|
|
|
|
def map_file(self, file, start): # Initialize with bytes from file
|
|
self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
|
|
self.read_cursor = start
|
|
def seek_file(self, position):
|
|
self.read_cursor = position
|
|
def close_file(self):
|
|
self.input.close()
|
|
|
|
def read_string(self):
|
|
# Strings are encoded depending on length:
|
|
# 0 to 252 : 1-byte-length followed by bytes (if any)
|
|
# 253 to 65,535 : byte'253' 2-byte-length followed by bytes
|
|
# 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
|
|
# ... and the Bitcoin client is coded to understand:
|
|
# greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
|
|
# ... but I don't think it actually handles any strings that big.
|
|
if self.input is None:
|
|
raise SerializationError("call write(bytes) before trying to deserialize")
|
|
|
|
try:
|
|
length = self.read_compact_size()
|
|
except IndexError:
|
|
raise SerializationError("attempt to read past end of buffer")
|
|
|
|
return self.read_bytes(length)
|
|
|
|
def write_string(self, string):
|
|
# Length-encoded as with read-string
|
|
self.write_compact_size(len(string))
|
|
self.write(string)
|
|
|
|
def read_bytes(self, length):
|
|
try:
|
|
result = self.input[self.read_cursor:self.read_cursor+length]
|
|
self.read_cursor += length
|
|
return result
|
|
except IndexError:
|
|
raise SerializationError("attempt to read past end of buffer")
|
|
|
|
return b''
|
|
|
|
def read_boolean(self): return self.read_bytes(1)[0] != chrsix(0)
|
|
def read_int16(self): return self._read_num('<h')
|
|
def read_uint16(self): return self._read_num('<H')
|
|
def read_int32(self): return self._read_num('<i')
|
|
def read_uint32(self): return self._read_num('<I')
|
|
def read_int64(self): return self._read_num('<q')
|
|
def read_uint64(self): return self._read_num('<Q')
|
|
|
|
def write_boolean(self, val): return self.write(chrsix(int(val)))
|
|
def write_int16(self, val): return self._write_num('<h', val)
|
|
def write_uint16(self, val): return self._write_num('<H', val)
|
|
def write_int32(self, val): return self._write_num('<i', val)
|
|
def write_uint32(self, val): return self._write_num('<I', val)
|
|
def write_int64(self, val): return self._write_num('<q', val)
|
|
def write_uint64(self, val): return self._write_num('<Q', val)
|
|
|
|
def read_compact_size(self):
|
|
size = ordsix(self.input[self.read_cursor])
|
|
self.read_cursor += 1
|
|
if size == 253:
|
|
size = self._read_num('<H')
|
|
elif size == 254:
|
|
size = self._read_num('<I')
|
|
elif size == 255:
|
|
size = self._read_num('<Q')
|
|
return size
|
|
|
|
def write_compact_size(self, size):
|
|
if size < 0:
|
|
raise SerializationError("attempt to write size < 0")
|
|
elif size < 253:
|
|
self.write(chrsix(size))
|
|
elif size < 2**16:
|
|
self.write('\xfd')
|
|
self._write_num('<H', size)
|
|
elif size < 2**32:
|
|
self.write('\xfe')
|
|
self._write_num('<I', size)
|
|
elif size < 2**64:
|
|
self.write('\xff')
|
|
self._write_num('<Q', size)
|
|
|
|
def _read_num(self, format):
|
|
(i,) = struct.unpack_from(format, self.input, self.read_cursor)
|
|
self.read_cursor += struct.calcsize(format)
|
|
return i
|
|
|
|
def _write_num(self, format, num):
|
|
s = struct.pack(format, num)
|
|
self.write(s)
|
|
|
|
def open_wallet(db_env, walletfile, writable=False):
|
|
db = DB(db_env)
|
|
if writable:
|
|
DB_TYPEOPEN = DB_CREATE
|
|
else:
|
|
DB_TYPEOPEN = DB_RDONLY
|
|
flags = DB_THREAD | DB_TYPEOPEN
|
|
try:
|
|
r = db.open(walletfile, "main", DB_BTREE, flags)
|
|
except DBError as e:
|
|
print(e)
|
|
r = True
|
|
|
|
if not(r is None):
|
|
logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.")
|
|
sys.exit(1)
|
|
|
|
return db
|
|
|
|
def parse_wallet(db, item_callback):
|
|
kds = BCDataStream()
|
|
vds = BCDataStream()
|
|
|
|
|
|
def parse_TxIn(vds):
|
|
d = Bdict({})
|
|
d['prevout_hash'] = binascii.hexlify(vds.read_bytes(32))
|
|
d['prevout_n'] = vds.read_uint32()
|
|
d['scriptSig'] = binascii.hexlify(vds.read_bytes(vds.read_compact_size()))
|
|
d['sequence'] = vds.read_uint32()
|
|
return d
|
|
|
|
|
|
def parse_TxOut(vds):
|
|
d = Bdict({})
|
|
d['value'] = vds.read_int64()//1e8
|
|
d['scriptPubKey'] = binascii.hexlify(vds.read_bytes(vds.read_compact_size()))
|
|
return d
|
|
|
|
|
|
for (key, value) in db.items():
|
|
d = Bdict({})
|
|
|
|
kds.clear(); kds.write(key)
|
|
vds.clear(); vds.write(value)
|
|
|
|
type = kds.read_string()
|
|
|
|
d["__key__"] = key
|
|
d["__value__"] = value
|
|
d["__type__"] = type
|
|
|
|
try:
|
|
if type == b"tx":
|
|
d["tx_id"] = binascii.hexlify(kds.read_bytes(32)[::-1])
|
|
start = vds.read_cursor
|
|
d['version'] = vds.read_int32()
|
|
n_vin = vds.read_compact_size()
|
|
d['txIn'] = []
|
|
for i in xrange(n_vin):
|
|
d['txIn'].append(parse_TxIn(vds))
|
|
n_vout = vds.read_compact_size()
|
|
d['txOut'] = []
|
|
for i in xrange(n_vout):
|
|
d['txOut'].append(parse_TxOut(vds))
|
|
d['lockTime'] = vds.read_uint32()
|
|
d['tx'] = binascii.hexlify(vds.input[start:vds.read_cursor])
|
|
d['txv'] = binascii.hexlify(value)
|
|
d['txk'] = binascii.hexlify(key)
|
|
elif type == b"name":
|
|
d['hash'] = kds.read_string()
|
|
d['name'] = vds.read_string()
|
|
elif type == b"version":
|
|
d['version'] = vds.read_uint32()
|
|
elif type == b"minversion":
|
|
d['minversion'] = vds.read_uint32()
|
|
elif type == b"setting":
|
|
d['setting'] = kds.read_string()
|
|
d['value'] = parse_setting(d['setting'], vds)
|
|
elif type == b"key":
|
|
d['public_key'] = kds.read_bytes(kds.read_compact_size())
|
|
d['private_key'] = vds.read_bytes(vds.read_compact_size())
|
|
elif type == b"wkey":
|
|
d['public_key'] = kds.read_bytes(kds.read_compact_size())
|
|
d['private_key'] = vds.read_bytes(vds.read_compact_size())
|
|
d['created'] = vds.read_int64()
|
|
d['expires'] = vds.read_int64()
|
|
d['comment'] = vds.read_string()
|
|
elif type == b"defaultkey":
|
|
d['key'] = vds.read_bytes(vds.read_compact_size())
|
|
elif type == b"pool":
|
|
d['n'] = kds.read_int64()
|
|
d['nVersion'] = vds.read_int32()
|
|
d['nTime'] = vds.read_int64()
|
|
d['public_key'] = vds.read_bytes(vds.read_compact_size())
|
|
elif type == b"acc":
|
|
d['account'] = kds.read_string()
|
|
d['nVersion'] = vds.read_int32()
|
|
d['public_key'] = vds.read_bytes(vds.read_compact_size())
|
|
elif type == b"acentry":
|
|
d['account'] = kds.read_string()
|
|
d['n'] = kds.read_uint64()
|
|
d['nVersion'] = vds.read_int32()
|
|
d['nCreditDebit'] = vds.read_int64()
|
|
d['nTime'] = vds.read_int64()
|
|
d['otherAccount'] = vds.read_string()
|
|
d['comment'] = vds.read_string()
|
|
# elif type == b"bestblock":
|
|
# d['nVersion'] = vds.read_int32()
|
|
# d.update(parse_BlockLocator(vds))
|
|
elif type == b"ckey":
|
|
d['public_key'] = kds.read_bytes(kds.read_compact_size())
|
|
d['encrypted_private_key'] = vds.read_bytes(vds.read_compact_size())
|
|
elif type == b"mkey":
|
|
d['nID'] = kds.read_uint32()
|
|
d['encrypted_key'] = vds.read_string()
|
|
d['salt'] = vds.read_string()
|
|
d['nDerivationMethod'] = vds.read_uint32()
|
|
d['nDerivationIterations'] = vds.read_uint32()
|
|
d['otherParams'] = vds.read_string()
|
|
|
|
item_callback(type, d)
|
|
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
print("ERROR parsing wallet.dat, type %s" % type)
|
|
print("key data: %s"%key)
|
|
print("key data in hex: %s"%binascii.hexlify(key))
|
|
print("value data in hex: %s"%binascii.hexlify(value))
|
|
sys.exit(1)
|
|
|
|
def delete_from_wallet(db_env, walletfile, typedel, kd):
|
|
db = open_wallet(db_env, walletfile, True)
|
|
kds = BCDataStream()
|
|
vds = BCDataStream()
|
|
|
|
deleted_items = 0
|
|
|
|
if not isinstance(kd, list):
|
|
kd=[kd]
|
|
|
|
if typedel=='tx' and kd!=['all']:
|
|
for keydel in kd:
|
|
db.delete('\x02\x74\x78'+binascii.unhexlify(keydel)[::-1])
|
|
deleted_items+=1
|
|
|
|
else:
|
|
for i,keydel in enumerate(kd):
|
|
for (key, value) in db.items():
|
|
kds.clear(); kds.write(key)
|
|
vds.clear(); vds.write(value)
|
|
type = kds.read_string()
|
|
|
|
if typedel == "tx" and type == b"tx":
|
|
db.delete(key)
|
|
deleted_items+=1
|
|
elif typedel == "key":
|
|
if type == b"key" or type == b"ckey":
|
|
if keydel == public_key_to_bc_address(kds.read_bytes(kds.read_compact_size())):
|
|
db.delete(key)
|
|
deleted_items+=1
|
|
elif type == b"pool":
|
|
vds.read_int32()
|
|
vds.read_int64()
|
|
if keydel == public_key_to_bc_address(vds.read_bytes(vds.read_compact_size())):
|
|
db.delete(key)
|
|
deleted_items+=1
|
|
elif type == b"name":
|
|
if keydel == kds.read_string():
|
|
db.delete(key)
|
|
deleted_items+=1
|
|
|
|
|
|
db.close()
|
|
return deleted_items
|
|
|
|
def merge_keys_lists(la, lb):
|
|
lr=Bdict({})
|
|
llr=[]
|
|
for k in la:
|
|
lr[k[0]]=k[1]
|
|
|
|
for k in lb:
|
|
if k[0] in lr.keys():
|
|
lr[k[0]]=lr[k[0]]+" / "+k[1]
|
|
else:
|
|
lr[k[0]]=k[1]
|
|
|
|
for k,j in lr.items():
|
|
llr.append([k,j])
|
|
|
|
return llr
|
|
|
|
def merge_wallets(wadir, wa, wbdir, wb, wrdir, wr, passphrase_a, passphrase_b, passphrase_r):
|
|
global passphrase
|
|
passphrase_LAST=passphrase
|
|
|
|
#Read Wallet 1
|
|
passphrase=passphrase_a
|
|
dba_env = create_env(wadir)
|
|
crypted_a = read_wallet(json_db, dba_env, wa, True, True, "", None)['crypted']
|
|
|
|
list_keys_a=[]
|
|
for i in json_db['keys']:
|
|
try:
|
|
label=i['label']
|
|
except:
|
|
label="#Reserve"
|
|
try:
|
|
list_keys_a.append([i['secret'], label])
|
|
except:
|
|
pass
|
|
|
|
if len(list_keys_a)==0:
|
|
return [False, "Something went wrong with the first wallet."]
|
|
|
|
|
|
#Read Wallet 2
|
|
passphrase=passphrase_b
|
|
dbb_env = create_env(wbdir)
|
|
crypted_b = read_wallet(json_db, dbb_env, wb, True, True, "", None)['crypted']
|
|
|
|
list_keys_b=[]
|
|
for i in json_db['keys']:
|
|
try:
|
|
label=i['label']
|
|
except:
|
|
label="#Reserve"
|
|
try:
|
|
list_keys_b.append([i['secret'], label])
|
|
except:
|
|
pass
|
|
if len(list_keys_b)==0:
|
|
return [False, "Something went wrong with the second wallet."]
|
|
|
|
m=merge_keys_lists(list_keys_a,list_keys_b)
|
|
|
|
|
|
#Create new wallet
|
|
dbr_env = create_env(wrdir)
|
|
create_new_wallet(dbr_env, wr, 80100)
|
|
|
|
dbr = open_wallet(dbr_env, wr, True)
|
|
update_wallet(dbr, 'minversion', { 'minversion' : 60000})
|
|
|
|
|
|
if len(passphrase_r)>0:
|
|
NPP_salt=os.urandom(8)
|
|
NPP_rounds=int(50000+random.random()*20000)
|
|
NPP_method=0
|
|
NPP_MK=os.urandom(32)
|
|
|
|
crypter.SetKeyFromPassphrase(passphrase_r, NPP_salt, NPP_rounds, NPP_method)
|
|
NPP_EMK = crypter.Encrypt(NPP_MK)
|
|
|
|
update_wallet(dbr, 'mkey', {
|
|
'encrypted_key': NPP_EMK,
|
|
'nDerivationIterations' : NPP_rounds,
|
|
'nDerivationMethod' : NPP_method,
|
|
'nID' : 1,
|
|
'otherParams' : b'',
|
|
'salt': NPP_salt
|
|
})
|
|
|
|
|
|
dbr.close()
|
|
|
|
t='\n'.join(map(lambda x:';'.join(x), m))
|
|
passphrase=passphrase_r
|
|
|
|
global global_merging_message
|
|
|
|
global_merging_message=["Merging...","Merging..."]
|
|
thread.start_new_thread(import_csv_keys, ("\x00"+t, wrdir, wr,))
|
|
t=""
|
|
|
|
passphrase=passphrase_LAST
|
|
|
|
return [True]
|
|
|
|
def random_string(l, alph="0123456789abcdef"):
|
|
r=""
|
|
la=len(alph)
|
|
for i in range(l):
|
|
r+=alph[int(la*(random.random()))]
|
|
return r
|
|
|
|
def update_wallet(db, types, datas, paramsAreLists=False):
|
|
"""Write a single item to the wallet.
|
|
db must be open with writable=True.
|
|
type and data are the type code and data dictionary as parse_wallet would
|
|
give to item_callback.
|
|
data's __key__, __value__ and __type__ are ignored; only the primary data
|
|
fields are used.
|
|
"""
|
|
|
|
if not paramsAreLists:
|
|
types=[types]
|
|
datas=[datas]
|
|
|
|
if len(types)!=len(datas):
|
|
raise Exception("UpdateWallet: sizes are different")
|
|
|
|
for it,type in enumerate(types):
|
|
type = str_to_bytes(type)
|
|
data=datas[it]
|
|
|
|
d = data
|
|
kds = BCDataStream()
|
|
vds = BCDataStream()
|
|
|
|
# Write the type code to the key
|
|
kds.write_string(type)
|
|
vds.write(b"") # Ensure there is something
|
|
|
|
try:
|
|
if type == b"tx":
|
|
# raise NotImplementedError("Writing items of type 'tx'")
|
|
kds.write(binascii.unhexlify(d['txi'][6:]))
|
|
vds.write(binascii.unhexlify(d['txv']))
|
|
elif type == b"name":
|
|
kds.write_string(d['hash'])
|
|
vds.write_string(d['name'])
|
|
elif type == b"version":
|
|
vds.write_uint32(d['version'])
|
|
elif type == b"minversion":
|
|
vds.write_uint32(d['minversion'])
|
|
elif type == b"setting":
|
|
raise NotImplementedError("Writing items of type 'setting'")
|
|
kds.write_string(d['setting'])
|
|
#d['value'] = parse_setting(d['setting'], vds)
|
|
elif type == b"key":
|
|
kds.write_string(d['public_key'])
|
|
vds.write_string(d['private_key'])
|
|
elif type == b"wkey":
|
|
kds.write_string(d['public_key'])
|
|
vds.write_string(d['private_key'])
|
|
vds.write_int64(d['created'])
|
|
vds.write_int64(d['expires'])
|
|
vds.write_string(d['comment'])
|
|
elif type == b"defaultkey":
|
|
vds.write_string(d['key'])
|
|
elif type == b"pool":
|
|
kds.write_int64(d['n'])
|
|
vds.write_int32(d['nVersion'])
|
|
vds.write_int64(d['nTime'])
|
|
vds.write_string(d['public_key'])
|
|
elif type == b"acc":
|
|
kds.write_string(d['account'])
|
|
vds.write_int32(d['nVersion'])
|
|
vds.write_string(d['public_key'])
|
|
elif type == b"acentry":
|
|
kds.write_string(d['account'])
|
|
kds.write_uint64(d['n'])
|
|
vds.write_int32(d['nVersion'])
|
|
vds.write_int64(d['nCreditDebit'])
|
|
vds.write_int64(d['nTime'])
|
|
vds.write_string(d['otherAccount'])
|
|
vds.write_string(d['comment'])
|
|
# elif type == b"bestblock":
|
|
# vds.write_int32(d['nVersion'])
|
|
# vds.write_compact_size(len(d['hashes']))
|
|
# for h in d['hashes']:
|
|
# vds.write(h)
|
|
elif type == b"ckey":
|
|
kds.write_string(d['public_key'])
|
|
vds.write_string(d['encrypted_private_key'])
|
|
elif type == b"mkey":
|
|
kds.write_uint32(d['nID'])
|
|
vds.write_string(d['encrypted_key'])
|
|
vds.write_string(d['salt'])
|
|
vds.write_uint32(d['nDerivationMethod'])
|
|
vds.write_uint32(d['nDerivationIterations'])
|
|
vds.write_string(d['otherParams'])
|
|
|
|
else:
|
|
print("Unknown key type: %s"%type)
|
|
|
|
# Write the key/value pair to the database
|
|
db.put(kds.input, vds.input)
|
|
|
|
except Exception as e:
|
|
print("ERROR writing to wallet.dat, type %s"%type)
|
|
print("data dictionary: %r"%data)
|
|
traceback.print_exc()
|
|
|
|
def create_new_wallet(db_env, walletfile, version):
|
|
db_out = DB(db_env)
|
|
|
|
try:
|
|
r = db_out.open(walletfile, "main", DB_BTREE, DB_CREATE)
|
|
except DBError:
|
|
r = True
|
|
|
|
if not(r is None):
|
|
logging.error("Couldn't open %s."%walletfile)
|
|
sys.exit(1)
|
|
|
|
db_out.put(binascii.unhexlify("0776657273696f6e"), binascii.unhexlify("%08x"%version)[::-1])
|
|
|
|
db_out.close()
|
|
|
|
|
|
def rewrite_wallet(db_env, walletfile, destFileName, pre_put_callback=None):
|
|
db = open_wallet(db_env, walletfile)
|
|
|
|
db_out = DB(db_env)
|
|
try:
|
|
r = db_out.open(destFileName, "main", DB_BTREE, DB_CREATE)
|
|
except DBError:
|
|
r = True
|
|
|
|
if not(r is None):
|
|
logging.error("Couldn't open %s."%destFileName)
|
|
sys.exit(1)
|
|
|
|
def item_callback(type, d):
|
|
if (pre_put_callback is None or pre_put_callback(type, d)):
|
|
db_out.put(d["__key__"], d["__value__"])
|
|
|
|
parse_wallet(db, item_callback)
|
|
db_out.close()
|
|
db.close()
|
|
|
|
# end of bitcointools wallet.dat handling code
|
|
|
|
# wallet.dat reader / writer
|
|
|
|
addr_to_keys={}
|
|
def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transactions, transaction_filter, include_balance, FillPool=False):
|
|
global passphrase, addr_to_keys
|
|
crypted=False
|
|
|
|
private_keys = []
|
|
private_hex_keys = []
|
|
|
|
db = open_wallet(db_env, walletfile, writable=FillPool)
|
|
|
|
json_db['keys'] = []
|
|
json_db['pool'] = []
|
|
json_db['tx'] = []
|
|
json_db['names'] = Bdict({})
|
|
json_db['ckey'] = []
|
|
json_db['mkey'] = Bdict({})
|
|
|
|
def item_callback(type, d):
|
|
if type == b"tx":
|
|
json_db['tx'].append({"tx_id" : d['tx_id'], "txin" : d['txIn'], "txout" : d['txOut'], "tx_v" : d['txv'], "tx_k" : d['txk']})
|
|
|
|
elif type == b"name":
|
|
json_db['names'][d['hash']] = d['name']
|
|
|
|
elif type == b"version":
|
|
json_db['version'] = d['version']
|
|
|
|
elif type == b"minversion":
|
|
json_db['minversion'] = d['minversion']
|
|
|
|
elif type == b"setting":
|
|
if not json_db.has_key('settings'):
|
|
json_db['settings'] = Bdict({})
|
|
json_db["settings"][d['setting']] = d['value']
|
|
|
|
elif type == b"defaultkey":
|
|
json_db['defaultkey'] = public_key_to_bc_address(d['key'])
|
|
|
|
elif type == b"key":
|
|
addr = public_key_to_bc_address(d['public_key'])
|
|
compressed = d['public_key'][0] != '\04'
|
|
sec = SecretToASecret(PrivKeyToSecret(d['private_key']), compressed)
|
|
hexsec = binascii.hexlify(ASecretToSecret(sec)[:32])
|
|
private_keys.append(sec)
|
|
addr_to_keys[addr]=[hexsec, binascii.hexlify(d['public_key'])]
|
|
json_db['keys'].append({'addr' : addr, 'sec' : sec, 'hexsec' : hexsec, 'secret' : hexsec, 'pubkey':binascii.hexlify(d['public_key']), 'compressed':compressed, 'private':binascii.hexlify(d['private_key'])})
|
|
|
|
elif type == b"wkey":
|
|
if not json_db.has_key('wkey'): json_db['wkey'] = []
|
|
json_db['wkey']['created'] = d['created']
|
|
|
|
elif type == b"pool":
|
|
""" d['n'] = kds.read_int64()
|
|
d['nVersion'] = vds.read_int32()
|
|
d['nTime'] = vds.read_int64()
|
|
d['public_key'] = vds.read_bytes(vds.read_compact_size())"""
|
|
try:
|
|
json_db['pool'].append( {'n': d['n'], 'addr': public_key_to_bc_address(d['public_key']), 'addr2': public_key_to_bc_address(binascii.unhexlify(d['public_key'])), 'addr3': public_key_to_bc_address(binascii.hexlify(d['public_key'])), 'nTime' : d['nTime'], 'nVersion' : d['nVersion'], 'public_key_hex' : d['public_key'] } )
|
|
except:
|
|
json_db['pool'].append( {'n': d['n'], 'addr': public_key_to_bc_address(d['public_key']), 'nTime' : d['nTime'], 'nVersion' : d['nVersion'], 'public_key_hex' : binascii.hexlify(d['public_key']) } )
|
|
|
|
elif type == b"acc":
|
|
json_db['acc'] = d['account']
|
|
# print("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key'])))
|
|
|
|
elif type == b"acentry":
|
|
json_db['acentry'] = (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])
|
|
|
|
# elif type == b"bestblock":
|
|
# json_db['bestblock'] = binascii.hexlify(d['hashes'][0][::-1])
|
|
|
|
elif type == b"ckey":
|
|
crypted=True
|
|
compressed = d['public_key'][0] != '\04'
|
|
json_db['keys'].append({ 'pubkey': binascii.hexlify(d['public_key']),'addr': public_key_to_bc_address(d['public_key']), 'encrypted_privkey': binascii.hexlify(d['encrypted_private_key']), 'compressed':compressed})
|
|
|
|
elif type == b"mkey":
|
|
json_db['mkey']['nID'] = d['nID']
|
|
json_db['mkey']['encrypted_key'] = binascii.hexlify(d['encrypted_key'])
|
|
json_db['mkey']['salt'] = binascii.hexlify(d['salt'])
|
|
json_db['mkey']['nDerivationMethod'] = d['nDerivationMethod']
|
|
json_db['mkey']['nDerivationIterations'] = d['nDerivationIterations']
|
|
json_db['mkey']['otherParams'] = d['otherParams']
|
|
|
|
if passphrase:
|
|
res = crypter.SetKeyFromPassphrase(passphrase, d['salt'], d['nDerivationIterations'], d['nDerivationMethod'])
|
|
if res == 0:
|
|
logging.error("Unsupported derivation method")
|
|
sys.exit(1)
|
|
masterkey = crypter.Decrypt(d['encrypted_key'])
|
|
crypter.SetKey(masterkey)
|
|
|
|
else:
|
|
json_db[type] = 'unsupported'
|
|
if type not in b'keymeta'.split():
|
|
print("Wallet data not recognized: %s"%str(d))
|
|
|
|
list_of_reserve_not_in_pool=[]
|
|
parse_wallet(db, item_callback)
|
|
|
|
|
|
nkeys = len(json_db['keys'])
|
|
i = 0
|
|
for k in json_db['keys']:
|
|
i+=1
|
|
addr = k['addr']
|
|
if include_balance:
|
|
# print("%3d/%d %s %s" % (i, nkeys, k["addr"], k["balance"]))
|
|
k["balance"] = balance(balance_site, k["addr"])
|
|
# print(" %s" % (i, nkeys, k["addr"], k["balance"]))
|
|
|
|
if addr in json_db['names'].keys():
|
|
k["label"] = json_db['names'][addr]
|
|
k["reserve"] = 0
|
|
else:
|
|
k["reserve"] = 1
|
|
list_of_reserve_not_in_pool.append(k['pubkey'])
|
|
|
|
|
|
def rnip_callback(a):
|
|
list_of_reserve_not_in_pool.remove(a['public_key_hex'])
|
|
|
|
if FillPool:
|
|
map(rnip_callback, json_db['pool'])
|
|
|
|
cpt=1
|
|
for p in list_of_reserve_not_in_pool:
|
|
update_wallet(db, 'pool', { 'public_key' : binascii.unhexlify(p), 'n' : cpt, 'nTime' : ts(), 'nVersion':80100 })
|
|
cpt+=1
|
|
|
|
|
|
|
|
db.close()
|
|
|
|
crypted = 'salt' in json_db['mkey']
|
|
|
|
if not crypted:
|
|
print("The wallet is not encrypted")
|
|
|
|
if crypted and not passphrase:
|
|
print("The wallet is encrypted but no passphrase is used")
|
|
|
|
if crypted and passphrase:
|
|
check = True
|
|
ppcorrect=True
|
|
for k in json_db['keys']:
|
|
if 'encrypted_privkey' in k:
|
|
ckey = binascii.unhexlify(k['encrypted_privkey'])
|
|
public_key = binascii.unhexlify(k['pubkey'])
|
|
crypter.SetIV(Hash(public_key))
|
|
secret = crypter.Decrypt(ckey)
|
|
compressed = public_key[0] != '\04'
|
|
|
|
|
|
if check:
|
|
check = False
|
|
pkey = EC_KEY(int(b'0x' + binascii.hexlify(secret), 16))
|
|
if public_key != GetPubKey(pkey, compressed):
|
|
print("The wallet is encrypted and the passphrase is incorrect")
|
|
ppcorrect=False
|
|
break
|
|
|
|
sec = SecretToASecret(secret, compressed)
|
|
k['sec'] = sec
|
|
k['hexsec'] = binascii.hexlify(secret[:32])
|
|
k['secret'] = binascii.hexlify(secret)
|
|
k['compressed'] = compressed
|
|
addr_to_keys[k['addr']]=[sec, k['pubkey']]
|
|
# del(k['ckey'])
|
|
# del(k['secret'])
|
|
# del(k['pubkey'])
|
|
private_keys.append(sec)
|
|
if ppcorrect:
|
|
print("The wallet is encrypted and the passphrase is correct")
|
|
|
|
for k in json_db['keys']:
|
|
if k['compressed'] and 'secret' in k:
|
|
k['secret']+=b"01"
|
|
|
|
# del(json_db['pool'])
|
|
# del(json_db['names'])
|
|
|
|
return {'crypted':crypted}
|
|
|
|
|
|
def parse_private_key(sec, force_compressed=None):
|
|
as_compressed = lambda x:x if force_compressed is None else force_compressed
|
|
try:
|
|
pkey = regenerate_key(sec)
|
|
compressed = as_compressed(is_compressed(sec))
|
|
except:
|
|
pkey = None
|
|
try:
|
|
binascii.unhexlify(sec)
|
|
except:
|
|
pass
|
|
if not pkey:
|
|
if len(sec) == 64:
|
|
pkey = EC_KEY(str_to_long(binascii.unhexlify(sec)))
|
|
compressed = as_compressed(False)
|
|
elif len(sec) == 66:
|
|
pkey = EC_KEY(str_to_long(binascii.unhexlify(sec[:-2])))
|
|
compressed = as_compressed(True)
|
|
else:
|
|
warnings.warn("Hexadecimal private keys must be 64 or 66 characters long (specified one is "+str(len(sec))+" characters long)")
|
|
if len(sec)<64:
|
|
compressed = as_compressed(False)
|
|
warnings.warn("Padding with zeroes, %scompressed"%('un' if not compressed else ''))
|
|
try:
|
|
pkey = EC_KEY(str_to_long(binascii.unhexlify('0'*(64-len(sec)) + sec)))
|
|
except Exception as e:
|
|
warnings.warn(e)
|
|
raise Exception("Failed padding with zeroes")
|
|
elif len(sec)>66:
|
|
compressed = as_compressed(False)
|
|
warnings.warn("Keeping first 64 characters, %scompressed"%('un' if not compressed else ''))
|
|
pkey = EC_KEY(str_to_long(binascii.unhexlify(sec[:64])))
|
|
else:
|
|
raise Exception("Error")
|
|
return (pkey, compressed)
|
|
|
|
def pubkey_info(pubkey, network):
|
|
addr = public_key_to_bc_address(pubkey, network.p2pkh_prefix)
|
|
p2wpkh = p2sh_script_to_addr(b'\x00\x14'+hash_160(pubkey))
|
|
witaddr = witprog_to_bech32_addr(hash_160(pubkey), network)
|
|
h160 = bc_address_to_hash_160(addr)
|
|
return addr, p2wpkh, witaddr, h160
|
|
|
|
def keyinfo(sec, network=None, print_info=False, force_compressed=None):
|
|
if not(network is None) and network.__class__ != Network:
|
|
network = find_network(network) or network
|
|
if sec.__class__ == Xpriv:
|
|
assert sec.ktype == 0
|
|
return keyinfo(binascii.hexlify(sec.key), network, print_info, force_compressed)
|
|
network = network or network_bitcoin
|
|
(pkey, compressed) = parse_private_key(sec, force_compressed)
|
|
if not pkey:
|
|
return False
|
|
|
|
secret = GetSecret(pkey)
|
|
private_key = GetPrivKey(pkey, compressed)
|
|
uncompressed_ser_public_key = GetPubKey(pkey, False)
|
|
ser_public_key = GetPubKey(pkey, compressed)
|
|
addr, p2wpkh, witaddr, h160 = pubkey_info(ser_public_key, network)
|
|
wif = SecretToASecret(secret, compressed) if network.wif_prefix else None
|
|
|
|
if print_info:
|
|
print("Network: %s"%network.name)
|
|
print("Compressed: %s"%str(compressed))
|
|
if network.p2pkh_prefix != None:
|
|
print("P2PKH Address: %s"%(addr))
|
|
if compressed:
|
|
if network.p2sh_prefix != None:
|
|
print("P2SH-P2WPKH Address: %s"%(p2wpkh))
|
|
else:
|
|
print("P2SH unavailable: unknown network P2SH prefix")
|
|
if compressed:
|
|
if network.segwit_hrp != None:
|
|
print("P2WPKH Address: %s"%(witaddr))
|
|
else:
|
|
print("P2WPKH unavailable: unknown network SegWit HRP")
|
|
if network.wif_prefix != None:
|
|
print("Privkey: %s"%wif)
|
|
else:
|
|
print("Privkey unavailable: unknown network WIF prefix")
|
|
print("Hexprivkey: %s"%bytes_to_str(binascii.hexlify(secret)))
|
|
if compressed:
|
|
warnings.warn(" For compressed keys, the hexadecimal private key sometimes contains an extra '01' at the end")
|
|
print("Hash160: %s"%bytes_to_str(binascii.hexlify(h160)))
|
|
print("Pubkey: %s"%bytes_to_str(binascii.hexlify(ser_public_key)))
|
|
if int(binascii.hexlify(secret), 16)>_r:
|
|
warnings.warn('/!\\ Beware, 0x%s is equivalent to 0x%.33x'%(binascii.hexlify(secret), int(binascii.hexlify(secret), 16)-_r))
|
|
|
|
r = KeyInfo(secret, private_key, ser_public_key, uncompressed_ser_public_key, addr, wif, compressed)
|
|
if network:
|
|
ki = network.keyinfo(r, print_info=print_info)
|
|
if ki:
|
|
addr = ki.addr
|
|
r = KeyInfo(secret, private_key, ser_public_key, uncompressed_ser_public_key, addr, wif, compressed)
|
|
return r
|
|
|
|
def importprivkey(db, sec, label, reserve, verbose=True):
|
|
k = keyinfo(sec, network, verbose)
|
|
secret = k.secret
|
|
private_key = k.private_key
|
|
public_key = k.public_key
|
|
addr = k.addr
|
|
|
|
global crypter, passphrase, json_db
|
|
crypted = False
|
|
if 'mkey' in json_db.keys() and 'salt' in json_db['mkey']:
|
|
crypted = True
|
|
if crypted:
|
|
if passphrase:
|
|
cry_master = binascii.unhexlify(json_db['mkey']['encrypted_key'])
|
|
cry_salt = binascii.unhexlify(json_db['mkey']['salt'])
|
|
cry_rounds = json_db['mkey']['nDerivationIterations']
|
|
cry_method = json_db['mkey']['nDerivationMethod']
|
|
|
|
crypter.SetKeyFromPassphrase(passphrase, cry_salt, cry_rounds, cry_method)
|
|
# if verbose:
|
|
# print("Import with", passphrase, "", binascii.hexlify(cry_master), "", binascii.hexlify(cry_salt))
|
|
masterkey = crypter.Decrypt(cry_master)
|
|
crypter.SetKey(masterkey)
|
|
crypter.SetIV(Hash(public_key))
|
|
e = crypter.Encrypt(secret)
|
|
ck_epk=e
|
|
|
|
update_wallet(db, 'ckey', { 'public_key' : public_key, 'encrypted_private_key' : ck_epk })
|
|
else:
|
|
update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key })
|
|
|
|
if not reserve:
|
|
update_wallet(db, 'name', { 'hash' : addr, 'name' : label })
|
|
|
|
|
|
return True
|
|
|
|
def balance(site, address):
|
|
page=urllib.urlopen("%s%s" % (site, address))
|
|
query_result=page.read()
|
|
#If the initial API call returned an error, use a secondary API
|
|
if query_result.startswith("error"):
|
|
page = urllib.urlopen("%s%s" % (backup_balance_site, address))
|
|
query_result = json.loads(page.read())["balance"]
|
|
return query_result
|
|
|
|
def read_jsonfile(filename):
|
|
filin = open(filename, 'r')
|
|
txdump = filin.read()
|
|
filin.close()
|
|
return json.loads(txdump)
|
|
|
|
def write_jsonfile(filename, array):
|
|
filout = open(filename, 'w')
|
|
filout.write(json.dumps(array, sort_keys=True, indent=0))
|
|
filout.close()
|
|
|
|
def export_all_keys(db, ks, filename):
|
|
txt=";".join(ks)+"\n"
|
|
for i in db['keys']:
|
|
try:
|
|
j=i.copy()
|
|
if 'label' not in j:
|
|
j['label']='#Reserve'
|
|
t=";".join([str(j[k]) for k in ks])
|
|
txt+=t+"\n"
|
|
except:
|
|
return False
|
|
|
|
try:
|
|
myFile = open(filename, 'w')
|
|
myFile.write(txt)
|
|
myFile.close()
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
def import_csv_keys(filename, wdir, wname, nbremax=9999999):
|
|
global global_merging_message
|
|
if filename[0]=="\x00": #yeah, dirty workaround
|
|
content=filename[1:]
|
|
else:
|
|
filen = open(filename, "r")
|
|
content = filen.read()
|
|
filen.close()
|
|
|
|
db_env = create_env(wdir)
|
|
read_wallet(json_db, db_env, wname, True, True, "", None)
|
|
db = open_wallet(db_env, wname, writable=True)
|
|
|
|
content=content.split('\n')
|
|
content=content[:min(nbremax, len(content))]
|
|
for i in range(len(content)):
|
|
c=content[i]
|
|
global_merging_message = ["Merging: "+str(round(100.0*(i+1)//len(content),1))+"%" for j in range(2)]
|
|
if ';' in c and len(c)>0 and c[0]!="#":
|
|
cs=c.split(';')
|
|
sec,label=cs[0:2]
|
|
reserve=False
|
|
if label=="#Reserve":
|
|
reserve=True
|
|
importprivkey(db, sec, label, reserve, verbose=False)
|
|
|
|
global_merging_message = ["Merging done.", ""]
|
|
|
|
db.close()
|
|
|
|
read_wallet(json_db, db_env, wname, True, True, "", None, True) #Fill the pool if empty
|
|
|
|
return True
|
|
|
|
def message_to_hash(msg, msgIsHex=False):
|
|
str = ""
|
|
# str += '04%064x%064x'%(pubkey.point.x(), pubkey.point.y())
|
|
# str += "Padding text - "
|
|
str += msg
|
|
if msgIsHex:
|
|
str = binascii.unhexlify(str)
|
|
hash = Hash(str)
|
|
return hash
|
|
|
|
def sign_message(secret, msg, msgIsHex=False):
|
|
k = KEY()
|
|
k.generate(secret)
|
|
return k.sign(message_to_hash(msg, msgIsHex))
|
|
|
|
def verify_message_signature(pubkey, sign, msg, msgIsHex=False):
|
|
k = KEY()
|
|
k.set_pubkey(binascii.unhexlify(pubkey))
|
|
return k.verify(message_to_hash(msg, msgIsHex), binascii.unhexlify(sign))
|
|
|
|
|
|
OP_DUP = 118;
|
|
OP_HASH160 = 169;
|
|
OP_EQUALVERIFY = 136;
|
|
OP_CHECKSIG = 172;
|
|
|
|
XOP_DUP = "%02x"%OP_DUP;
|
|
XOP_HASH160 = "%02x"%OP_HASH160;
|
|
XOP_EQUALVERIFY = "%02x"%OP_EQUALVERIFY;
|
|
XOP_CHECKSIG = "%02x"%OP_CHECKSIG;
|
|
|
|
BTC = 1e8
|
|
|
|
def ct(l_prevh, l_prevn, l_prevsig, l_prevpubkey, l_value_out, l_pubkey_out, is_msg_to_sign=-1, oldScriptPubkey=""):
|
|
scriptSig = True
|
|
if is_msg_to_sign != -1:
|
|
scriptSig = False
|
|
index = is_msg_to_sign
|
|
|
|
ret = ""
|
|
ret += inverse_str("%08x"%1)
|
|
nvin = len(l_prevh)
|
|
ret += "%02x"%nvin
|
|
|
|
for i in range(nvin):
|
|
txin_ret = ""
|
|
txin_ret2 = ""
|
|
|
|
txin_ret += inverse_str(l_prevh[i])
|
|
txin_ret += inverse_str("%08x"%l_prevn[i])
|
|
|
|
if scriptSig:
|
|
txin_ret2 += "%02x"%(1+len(l_prevsig[i])//2)
|
|
txin_ret2 += l_prevsig[i]
|
|
txin_ret2 += "01"
|
|
txin_ret2 += "%02x"%(len(l_prevpubkey[i])//2)
|
|
txin_ret2 += l_prevpubkey[i]
|
|
|
|
txin_ret += "%02x"%(len(txin_ret2)//2)
|
|
txin_ret += txin_ret2
|
|
|
|
elif index == i:
|
|
txin_ret += "%02x"%(len(oldScriptPubkey)//2)
|
|
txin_ret += oldScriptPubkey
|
|
else:
|
|
txin_ret += "00"
|
|
|
|
ret += txin_ret
|
|
ret += "ffffffff"
|
|
|
|
|
|
nvout = len(l_value_out)
|
|
ret += "%02x"%nvout
|
|
for i in range(nvout):
|
|
txout_ret = ""
|
|
|
|
txout_ret += inverse_str("%016x"%(l_value_out[i]))
|
|
txout_ret += "%02x"%(len(l_pubkey_out[i])//2+5)
|
|
txout_ret += "%02x"%OP_DUP
|
|
txout_ret += "%02x"%OP_HASH160
|
|
txout_ret += "%02x"%(len(l_pubkey_out[i])//2)
|
|
txout_ret += l_pubkey_out[i]
|
|
txout_ret += "%02x"%OP_EQUALVERIFY
|
|
txout_ret += "%02x"%OP_CHECKSIG
|
|
ret += txout_ret
|
|
|
|
ret += "00000000"
|
|
if not scriptSig:
|
|
ret += "01000000"
|
|
return ret
|
|
|
|
def create_transaction(secret_key, hashes_txin, indexes_txin, pubkey_txin, prevScriptPubKey, amounts_txout, scriptPubkey):
|
|
li1 = len(secret_key)
|
|
li2 = len(hashes_txin)
|
|
li3 = len(indexes_txin)
|
|
li4 = len(pubkey_txin)
|
|
li5 = len(prevScriptPubKey)
|
|
|
|
if li1 != li2 or li2 != li3 or li3 != li4 or li4 != li5:
|
|
print("Error in the number of tx inputs")
|
|
exit(0)
|
|
|
|
lo1 = len(amounts_txout)
|
|
lo2 = len(scriptPubkey)
|
|
|
|
if lo1 != lo2:
|
|
print("Error in the number of tx outputs")
|
|
exit(0)
|
|
|
|
sig_txin = []
|
|
i=0
|
|
for cpt in hashes_txin:
|
|
sig_txin.append(sign_message(binascii.unhexlify(secret_key[i]), ct(hashes_txin, indexes_txin, sig_txin, pubkey_txin, amounts_txout, scriptPubkey, i, prevScriptPubKey[i]), True)+"01")
|
|
i+=1
|
|
|
|
tx = ct(hashes_txin, indexes_txin, sig_txin, pubkey_txin, amounts_txout, scriptPubkey)
|
|
hashtx = binascii.hexlify(Hash(binascii.unhexlify(tx)))
|
|
|
|
for i in range(len(sig_txin)):
|
|
try:
|
|
verify_message_signature(pubkey_txin[i], sig_txin[i][:-2], ct(hashes_txin, indexes_txin, sig_txin, pubkey_txin, amounts_txout, scriptPubkey, i, prevScriptPubKey[i]), True)
|
|
print("sig %2d: verif ok"%i)
|
|
except:
|
|
print("sig %2d: verif error"%i)
|
|
exit(0)
|
|
|
|
# tx += end_of_wallettx([], int(time.time()))
|
|
# return [inverse_str(hashtx), "027478" + hashtx, tx]
|
|
return [inverse_str(hashtx), "", tx]
|
|
|
|
def inverse_str(string):
|
|
ret = ""
|
|
for i in range(len(string)//2):
|
|
ret += string[len(string)-2-2*i];
|
|
ret += string[len(string)-2-2*i+1];
|
|
return ret
|
|
|
|
def read_table(table, beg, end):
|
|
rows = table.split(beg)
|
|
for i in range(len(rows)):
|
|
rows[i] = rows[i].split(end)[0]
|
|
return rows
|
|
|
|
def read_blockexplorer_table(table):
|
|
cell = []
|
|
rows = read_table(table, '<tr>', '</tr>')
|
|
for i in range(len(rows)):
|
|
cell.append(read_table(rows[i], '<td>', '</td>'))
|
|
del cell[i][0]
|
|
del cell[0]
|
|
del cell[0]
|
|
return cell
|
|
|
|
txin_amounts = Bdict({})
|
|
|
|
def bc_address_to_available_tx(address, testnet=False):
|
|
TN=""
|
|
if testnet:
|
|
TN="testnet"
|
|
|
|
blockexplorer_url = "http://blockexplorer.com/"+TN+"/address/"
|
|
ret = ""
|
|
txin = []
|
|
txin_no = Bdict({})
|
|
global txin_amounts
|
|
txout = []
|
|
balance = 0
|
|
txin_is_used = Bdict({})
|
|
|
|
page = urllib.urlopen("%s/%s" % (blockexplorer_url, address))
|
|
try:
|
|
table = page.read().split('<table class="txtable">')[1]
|
|
table = table.split("</table>")[0]
|
|
except:
|
|
return {address:[]}
|
|
|
|
cell = read_blockexplorer_table(table)
|
|
|
|
for i in range(len(cell)):
|
|
txhash = read_table(cell[i][0], '/tx/', '#')[1]
|
|
post_hash = read_table(cell[i][0], '#', '">')[1]
|
|
io = post_hash[0]
|
|
no_tx = post_hash[1:]
|
|
if io in 'i':
|
|
txout.append([txhash, post_hash])
|
|
else:
|
|
txin.append(txhash+no_tx)
|
|
txin_no[txhash+no_tx] = post_hash[1:]
|
|
txin_is_used[txhash+no_tx] = 0
|
|
|
|
#hashblock = read_table(cell[i][1], '/block/', '">')[1]
|
|
#blocknumber = read_table(cell[i][1], 'Block ', '</a>')[1]
|
|
|
|
txin_amounts[txhash+no_tx] = round(float(cell[i][2]), 8)
|
|
|
|
# if cell[i][3][:4] in 'Sent' and io in 'o':
|
|
# print(cell[i][3][:4])
|
|
# print(io)
|
|
# return 'error'
|
|
# if cell[i][3][:4] in 'Rece' and io in 'i':
|
|
# print(cell[i][3][:4])
|
|
# print(io)
|
|
# return 'error'
|
|
|
|
balance = round(float(cell[i][5]), 8)
|
|
|
|
|
|
for tx in txout:
|
|
pagetx = urllib.urlopen("http://blockexplorer.com/"+TN+"/tx/"+tx[0])
|
|
table_in = pagetx.read().split('<a name="outputs">Outputs</a>')[0].split('<table class="txtable">')[1].split("</table>")[0]
|
|
|
|
cell = read_blockexplorer_table(table_in)
|
|
for i in range(len(cell)):
|
|
txhash = read_table(cell[i][0], '/tx/', '#')[1]
|
|
no_tx = read_table(cell[i][0], '#', '">')[1][1:]
|
|
|
|
if txhash+no_tx in txin:
|
|
txin_is_used[txhash+no_tx] = 1
|
|
|
|
ret = []
|
|
for tx in txin:
|
|
if not txin_is_used[tx]:
|
|
ret.append([tx,txin_amounts[tx],txin_no[tx]])
|
|
|
|
return {address : ret}
|
|
|
|
empty_txin=Bdict({'hash':'', 'index':'', 'sig':'##', 'pubkey':'', 'oldscript':'', 'addr':''})
|
|
empty_txout=Bdict({'amount':'', 'script':''})
|
|
|
|
class tx():
|
|
ins=[]
|
|
outs=[]
|
|
tosign=False
|
|
|
|
def hashtypeone(index,script):
|
|
global empty_txin
|
|
for i in range(len(ins)):
|
|
self.ins[i]=empty_txin
|
|
self.ins[index]['pubkey']=""
|
|
self.ins[index]['oldscript']=s
|
|
self.tosign=True
|
|
|
|
def copy():
|
|
r=tx()
|
|
r.ins=self.ins[:]
|
|
r.outs=self.outs[:]
|
|
return r
|
|
|
|
def sign(n=-1):
|
|
if n==-1:
|
|
for i in range(len(ins)):
|
|
self.sign(i)
|
|
return "done"
|
|
|
|
global json_db
|
|
txcopy=self.copy()
|
|
txcopy.hashtypeone(i, self.ins[n]['oldscript'])
|
|
|
|
sec=''
|
|
for k in json_db['keys']:
|
|
if k['addr']==self.ins[n]['addr'] and 'hexsec' in k:
|
|
sec=k['hexsec']
|
|
if sec=='':
|
|
print("priv key not found (addr:"+self.ins[n]['addr']+")")
|
|
return ""
|
|
|
|
self.ins[n]['sig']=sign_message(binascii.unhexlify(sec), txcopy.get_tx(), True)
|
|
|
|
def ser():
|
|
r=Bdict({})
|
|
r['ins']=self.ins
|
|
r['outs']=self.outs
|
|
r['tosign']=self.tosign
|
|
return json.dumps(r)
|
|
|
|
def unser(r):
|
|
s=json.loads(r)
|
|
self.ins=s['ins']
|
|
self.outs=s['outs']
|
|
self.tosign=s['tosign']
|
|
|
|
def get_tx():
|
|
r=''
|
|
ret += inverse_str("%08x"%1)
|
|
ret += "%02x"%len(self.ins)
|
|
|
|
for i in range(len(self.ins)):
|
|
txin=self.ins[i]
|
|
ret += inverse_str(txin['hash'])
|
|
ret += inverse_str("%08x"%txin['index'])
|
|
|
|
if txin['pubkey']!="":
|
|
tmp += "%02x"%(1+len(txin['sig'])//2)
|
|
tmp += txin['sig']
|
|
tmp += "01"
|
|
tmp += "%02x"%(len(txin['pubkey'])//2)
|
|
tmp += txin['pubkey']
|
|
|
|
ret += "%02x"%(len(tmp)/2)
|
|
ret += tmp
|
|
|
|
elif txin['oldscript']!="":
|
|
ret += "%02x"%(len(txin['oldscript'])//2)
|
|
ret += txin['oldscript']
|
|
|
|
else:
|
|
ret += "00"
|
|
|
|
ret += "ffffffff"
|
|
|
|
ret += "%02x"%len(self.outs)
|
|
|
|
for i in range(len(self.outs)):
|
|
txout=self.outs[i]
|
|
ret += inverse_str("%016x"%(txout['amount']))
|
|
|
|
if txout['script'][:2]=='s:': #script
|
|
script=txout['script'][:2]
|
|
ret += "%02x"%(len(script)//2)
|
|
ret += script
|
|
else: #address
|
|
ret += "%02x"%(len(txout['script'])//2+5)
|
|
ret += "%02x"%OP_DUP
|
|
ret += "%02x"%OP_HASH160
|
|
ret += "%02x"%(len(txout['script'])//2)
|
|
ret += txout['script']
|
|
ret += "%02x"%OP_EQUALVERIFY
|
|
ret += "%02x"%OP_CHECKSIG
|
|
|
|
ret += "00000000"
|
|
if not self.tosign:
|
|
ret += "01000000"
|
|
return ret
|
|
|
|
|
|
def update_pyw():
|
|
if md5_last_pywallet[0] and md5_last_pywallet[1] not in md5_pywallet:
|
|
dl=urllib.urlopen('https://raw.github.com/jackjack-jj/pywallet/master/pywallet.py').read()
|
|
if len(dl)>40 and md5_2(dl)==md5_last_pywallet[1]:
|
|
filout = open(pyw_path+"/"+pyw_filename, 'w')
|
|
filout.write(dl)
|
|
filout.close()
|
|
else:
|
|
return "Problem when downloading new version ("+md5_2(dl)+"/"+md5_last_pywallet[1]+")"
|
|
|
|
def clone_wallet(parentPath, clonePath):
|
|
types,datas=[],[]
|
|
parentdir,parentname=os.path.split(parentPath)
|
|
wdir,wname=os.path.split(clonePath)
|
|
|
|
db_env = create_env(parentdir)
|
|
read_wallet(json_db, db_env, parentname, True, True, "", False)
|
|
|
|
types.append('version')
|
|
datas.append({'version':json_db['version']})
|
|
types.append('defaultkey')
|
|
datas.append({'key':json_db['defaultkey']})
|
|
for k in json_db['keys']:
|
|
types.append('ckey')
|
|
datas.append({'public_key':binascii.unhexlify(k['pubkey']),'encrypted_private_key':binascii.unhexlify(random_string(96))})
|
|
for k in json_db['pool']:
|
|
types.append('pool')
|
|
datas.append({'n':k['n'],'nVersion':k['nVersion'],'nTime':k['nTime'],'public_key':binascii.unhexlify(k['public_key_hex'])})
|
|
for addr,label in json_db['names'].items():
|
|
types.append('name')
|
|
datas.append({'hash':addr,'name':'Watch:'+label})
|
|
|
|
db_env = create_env(wdir)
|
|
create_new_wallet(db_env, wname, 60000)
|
|
|
|
db = open_wallet(db_env, wname, True)
|
|
NPP_salt = binascii.unhexlify(random_string(16))
|
|
NPP_rounds = int(50000+random.random()*20000)
|
|
NPP_method = 0
|
|
NPP_MK = binascii.unhexlify(random_string(64))
|
|
crypter.SetKeyFromPassphrase(random_string(64), NPP_salt, NPP_rounds, NPP_method)
|
|
NPP_EMK = crypter.Encrypt(NPP_MK)
|
|
update_wallet(db, 'mkey', {
|
|
"encrypted_key": NPP_EMK,
|
|
'nDerivationIterations' : NPP_rounds,
|
|
'nDerivationMethod' : NPP_method,
|
|
'nID' : 1,
|
|
'otherParams' : b'',
|
|
"salt": NPP_salt
|
|
})
|
|
db.close()
|
|
|
|
read_wallet(json_db, db_env, wname, True, True, "", False)
|
|
|
|
db = open_wallet(db_env, wname, writable=True)
|
|
update_wallet(db, types, datas, True)
|
|
db.close()
|
|
print("Wallet successfully cloned to:\n %s"%clonePath)
|
|
|
|
md5_last_pywallet = [False, ""]
|
|
|
|
def retrieve_last_pywallet_md5():
|
|
global md5_last_pywallet
|
|
md5_last_pywallet = [True, md5_onlinefile('https://raw.github.com/jackjack-jj/pywallet/master/pywallet.py')]
|
|
|
|
from optparse import OptionParser
|
|
|
|
def bech32_polymod(values):
|
|
GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
|
chk = 1
|
|
for v in values:
|
|
b = (chk >> 25)
|
|
chk = (chk & 0x1ffffff) << 5 ^ v
|
|
for i in range(5):
|
|
chk ^= GEN[i] if ((b >> i) & 1) else 0
|
|
return chk
|
|
|
|
def bech32_hrp_expand(s):
|
|
return [ordsix(x) >> 5 for x in s] + [0] + [ordsix(x) & 31 for x in s]
|
|
|
|
def bech32_verify_checksum(hrp, data):
|
|
return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1
|
|
|
|
def bech32_create_checksum(hrp, data):
|
|
values = bech32_hrp_expand(hrp) + data
|
|
polymod = bech32_polymod(values + [0,0,0,0,0,0]) ^ 1
|
|
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
|
|
|
|
BECH32_ALPH='qpzry9x8gf2tvdw0s3jn54khce6mua7l'
|
|
BASE32_ALPH='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
|
|
|
def witprog_to_bech32_addr(witprog, network, witv=0):
|
|
x = base64.b32encode(witprog)
|
|
if PY3:x = x.decode()
|
|
x = x.replace('=', '')
|
|
data = [witv]+list(map(lambda y:BASE32_ALPH.index(y), x))
|
|
combined = data + bech32_create_checksum(network.segwit_hrp, data)
|
|
addr = network.segwit_hrp+'1'+''.join([BECH32_ALPH[d] for d in combined])
|
|
return addr
|
|
|
|
def p2sh_script_to_addr(script):
|
|
version=5
|
|
return hash_160_to_bc_address(hash_160(script), version)
|
|
|
|
def whitepaper():
|
|
try:
|
|
rawtx = subprocess.check_output(["bitcoin-cli", "getrawtransaction", "54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713"])
|
|
except:
|
|
rawtx = urllib.urlopen("https://blockchain.info/tx/54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713?format=hex").read()
|
|
outputs = rawtx.split("0100000000000000")
|
|
pdf = b""
|
|
for output in outputs[1:-2]:
|
|
i = 6
|
|
pdf += binascii.unhexlify(output[i:i+130])
|
|
i += 132
|
|
pdf += binascii.unhexlify(output[i:i+130])
|
|
i += 132
|
|
pdf += binascii.unhexlify(output[i:i+130])
|
|
pdf += binascii.unhexlify(outputs[-2][6:-4])
|
|
content = pdf[8:-8]
|
|
assert hashlib.sha256(content).hexdigest() == 'b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553'
|
|
filename = 'bitcoin_whitepaper'
|
|
while os.path.exists(filename+'.pdf'):
|
|
filename += '_'
|
|
with open(filename+'.pdf', "wb") as f:
|
|
f.write(content)
|
|
print("Wrote the Bitcoin whitepaper to %s.pdf"%filename)
|
|
|
|
bip39_wordlist = '''
|
|
abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve acid acoustic acquire across act action actor actress actual adapt add addict address adjust admit adult advance advice aerobic affair afford afraid again age agent agree ahead aim air
|
|
airport aisle alarm album alcohol alert alien all alley allow almost alone alpha already also alter always amateur amazing among amount amused analyst anchor ancient anger angle angry animal ankle announce annual another answer antenna antique anxiety any apart apology appear apple approve april
|
|
arch arctic area arena argue arm armed armor army around arrange arrest arrive arrow art artefact artist artwork ask aspect assault asset assist assume asthma athlete atom attack attend attitude attract auction audit august aunt author auto autumn average avocado avoid awake aware away awesome
|
|
awful awkward axis baby bachelor bacon badge bag balance balcony ball bamboo banana banner bar barely bargain barrel base basic basket battle beach bean beauty because become beef before begin behave behind believe below belt bench benefit best betray better between beyond bicycle bid bike bind
|
|
biology bird birth bitter black blade blame blanket blast bleak bless blind blood blossom blouse blue blur blush board boat body boil bomb bone bonus book boost border boring borrow boss bottom bounce box boy bracket brain brand brass brave bread breeze brick bridge brief bright bring brisk
|
|
broccoli broken bronze broom brother brown brush bubble buddy budget buffalo build bulb bulk bullet bundle bunker burden burger burst bus business busy butter buyer buzz cabbage cabin cable cactus cage cake call calm camera camp can canal cancel candy cannon canoe canvas canyon capable capital
|
|
captain car carbon card cargo carpet carry cart case cash casino castle casual cat catalog catch category cattle caught cause caution cave ceiling celery cement census century cereal certain chair chalk champion change chaos chapter charge chase chat cheap check cheese chef cherry chest chicken
|
|
chief child chimney choice choose chronic chuckle chunk churn cigar cinnamon circle citizen city civil claim clap clarify claw clay clean clerk clever click client cliff climb clinic clip clock clog close cloth cloud clown club clump cluster clutch coach coast coconut code coffee coil coin collect
|
|
color column combine come comfort comic common company concert conduct confirm congress connect consider control convince cook cool copper copy coral core corn correct cost cotton couch country couple course cousin cover coyote crack cradle craft cram crane crash crater crawl crazy cream credit
|
|
creek crew cricket crime crisp critic crop cross crouch crowd crucial cruel cruise crumble crunch crush cry crystal cube culture cup cupboard curious current curtain curve cushion custom cute cycle dad damage damp dance danger daring dash daughter dawn day deal debate debris decade december decide
|
|
decline decorate decrease deer defense define defy degree delay deliver demand demise denial dentist deny depart depend deposit depth deputy derive describe desert design desk despair destroy detail detect develop device devote diagram dial diamond diary dice diesel diet differ digital dignity
|
|
dilemma dinner dinosaur direct dirt disagree discover disease dish dismiss disorder display distance divert divide divorce dizzy doctor document dog doll dolphin domain donate donkey donor door dose double dove draft dragon drama drastic draw dream dress drift drill drink drip drive drop drum dry
|
|
duck dumb dune during dust dutch duty dwarf dynamic eager eagle early earn earth easily east easy echo ecology economy edge edit educate effort egg eight either elbow elder electric elegant element elephant elevator elite else embark embody embrace emerge emotion employ empower empty enable enact
|
|
end endless endorse enemy energy enforce engage engine enhance enjoy enlist enough enrich enroll ensure enter entire entry envelope episode equal equip era erase erode erosion error erupt escape essay essence estate eternal ethics evidence evil evoke evolve exact example excess exchange excite
|
|
exclude excuse execute exercise exhaust exhibit exile exist exit exotic expand expect expire explain expose express extend extra eye eyebrow fabric face faculty fade faint faith fall false fame family famous fan fancy fantasy farm fashion fat fatal father fatigue fault favorite feature february
|
|
federal fee feed feel female fence festival fetch fever few fiber fiction field figure file film filter final find fine finger finish fire firm first fiscal fish fit fitness fix flag flame flash flat flavor flee flight flip float flock floor flower fluid flush fly foam focus fog foil fold follow
|
|
food foot force forest forget fork fortune forum forward fossil foster found fox fragile frame frequent fresh friend fringe frog front frost frown frozen fruit fuel fun funny furnace fury future gadget gain galaxy gallery game gap garage garbage garden garlic garment gas gasp gate gather gauge
|
|
gaze general genius genre gentle genuine gesture ghost giant gift giggle ginger giraffe girl give glad glance glare glass glide glimpse globe gloom glory glove glow glue goat goddess gold good goose gorilla gospel gossip govern gown grab grace grain grant grape grass gravity great green grid grief
|
|
grit grocery group grow grunt guard guess guide guilt guitar gun gym habit hair half hammer hamster hand happy harbor hard harsh harvest hat have hawk hazard head health heart heavy hedgehog height hello helmet help hen hero hidden high hill hint hip hire history hobby hockey hold hole holiday
|
|
hollow home honey hood hope horn horror horse hospital host hotel hour hover hub huge human humble humor hundred hungry hunt hurdle hurry hurt husband hybrid ice icon idea identify idle ignore ill illegal illness image imitate immense immune impact impose improve impulse inch include income
|
|
increase index indicate indoor industry infant inflict inform inhale inherit initial inject injury inmate inner innocent input inquiry insane insect inside inspire install intact interest into invest invite involve iron island isolate issue item ivory jacket jaguar jar jazz jealous jeans jelly
|
|
jewel job join joke journey joy judge juice jump jungle junior junk just kangaroo keen keep ketchup key kick kid kidney kind kingdom kiss kit kitchen kite kitten kiwi knee knife knock know lab label labor ladder lady lake lamp language laptop large later latin laugh laundry lava law lawn lawsuit
|
|
layer lazy leader leaf learn leave lecture left leg legal legend leisure lemon lend length lens leopard lesson letter level liar liberty library license life lift light like limb limit link lion liquid list little live lizard load loan lobster local lock logic lonely long loop lottery loud lounge
|
|
love loyal lucky luggage lumber lunar lunch luxury lyrics machine mad magic magnet maid mail main major make mammal man manage mandate mango mansion manual maple marble march margin marine market marriage mask mass master match material math matrix matter maximum maze meadow mean measure meat
|
|
mechanic medal media melody melt member memory mention menu mercy merge merit merry mesh message metal method middle midnight milk million mimic mind minimum minor minute miracle mirror misery miss mistake mix mixed mixture mobile model modify mom moment monitor monkey monster month moon moral
|
|
more morning mosquito mother motion motor mountain mouse move movie much muffin mule multiply muscle museum mushroom music must mutual myself mystery myth naive name napkin narrow nasty nation nature near neck need negative neglect neither nephew nerve nest net network neutral never news next nice
|
|
night noble noise nominee noodle normal north nose notable note nothing notice novel now nuclear number nurse nut oak obey object oblige obscure observe obtain obvious occur ocean october odor off offer office often oil okay old olive olympic omit once one onion online only open opera opinion
|
|
oppose option orange orbit orchard order ordinary organ orient original orphan ostrich other outdoor outer output outside oval oven over own owner oxygen oyster ozone pact paddle page pair palace palm panda panel panic panther paper parade parent park parrot party pass patch path patient patrol
|
|
pattern pause pave payment peace peanut pear peasant pelican pen penalty pencil people pepper perfect permit person pet phone photo phrase physical piano picnic picture piece pig pigeon pill pilot pink pioneer pipe pistol pitch pizza place planet plastic plate play please pledge pluck plug plunge
|
|
poem poet point polar pole police pond pony pool popular portion position possible post potato pottery poverty powder power practice praise predict prefer prepare present pretty prevent price pride primary print priority prison private prize problem process produce profit program project promote
|
|
proof property prosper protect proud provide public pudding pull pulp pulse pumpkin punch pupil puppy purchase purity purpose purse push put puzzle pyramid quality quantum quarter question quick quit quiz quote rabbit raccoon race rack radar radio rail rain raise rally ramp ranch random range
|
|
rapid rare rate rather raven raw razor ready real reason rebel rebuild recall receive recipe record recycle reduce reflect reform refuse region regret regular reject relax release relief rely remain remember remind remove render renew rent reopen repair repeat replace report require rescue
|
|
resemble resist resource response result retire retreat return reunion reveal review reward rhythm rib ribbon rice rich ride ridge rifle right rigid ring riot ripple risk ritual rival river road roast robot robust rocket romance roof rookie room rose rotate rough round route royal rubber rude rug
|
|
rule run runway rural sad saddle sadness safe sail salad salmon salon salt salute same sample sand satisfy satoshi sauce sausage save say scale scan scare scatter scene scheme school science scissors scorpion scout scrap screen script scrub sea search season seat second secret section security
|
|
seed seek segment select sell seminar senior sense sentence series service session settle setup seven shadow shaft shallow share shed shell sheriff shield shift shine ship shiver shock shoe shoot shop short shoulder shove shrimp shrug shuffle shy sibling sick side siege sight sign silent silk
|
|
silly silver similar simple since sing siren sister situate six size skate sketch ski skill skin skirt skull slab slam sleep slender slice slide slight slim slogan slot slow slush small smart smile smoke smooth snack snake snap sniff snow soap soccer social sock soda soft solar soldier solid
|
|
solution solve someone song soon sorry sort soul sound soup source south space spare spatial spawn speak special speed spell spend sphere spice spider spike spin spirit split spoil sponsor spoon sport spot spray spread spring spy square squeeze squirrel stable stadium staff stage stairs stamp
|
|
stand start state stay steak steel stem step stereo stick still sting stock stomach stone stool story stove strategy street strike strong struggle student stuff stumble style subject submit subway success such sudden suffer sugar suggest suit summer sun sunny sunset super supply supreme sure
|
|
surface surge surprise surround survey suspect sustain swallow swamp swap swarm swear sweet swift swim swing switch sword symbol symptom syrup system table tackle tag tail talent talk tank tape target task taste tattoo taxi teach team tell ten tenant tennis tent term test text thank that theme
|
|
then theory there they thing this thought three thrive throw thumb thunder ticket tide tiger tilt timber time tiny tip tired tissue title toast tobacco today toddler toe together toilet token tomato tomorrow tone tongue tonight tool tooth top topic topple torch tornado tortoise toss total tourist
|
|
toward tower town toy track trade traffic tragic train transfer trap trash travel tray treat tree trend trial tribe trick trigger trim trip trophy trouble truck true truly trumpet trust truth try tube tuition tumble tuna tunnel turkey turn turtle twelve twenty twice twin twist two type typical
|
|
ugly umbrella unable unaware uncle uncover under undo unfair unfold unhappy uniform unique unit universe unknown unlock until unusual unveil update upgrade uphold upon upper upset urban urge usage use used useful useless usual utility vacant vacuum vague valid valley valve van vanish vapor various
|
|
vast vault vehicle velvet vendor venture venue verb verify version very vessel veteran viable vibrant vicious victory video view village vintage violin virtual virus visa visit visual vital vivid vocal voice void volcano volume vote voyage wage wagon wait walk wall walnut want warfare warm warrior
|
|
wash wasp waste water wave way wealth weapon wear weasel weather web wedding weekend weird welcome west wet whale what wheat wheel when where whip whisper wide width wife wild will win window wine wing wink winner winter wire wisdom wise wish witness wolf woman wonder wood wool word work world
|
|
worry worth wrap wreck wrestle wrist write wrong yard year yellow you young youth zebra zero zone zoo
|
|
'''.split()
|
|
|
|
PBKDF2_ROUNDS = 2048
|
|
def binary_search(a, x, lo, hi):
|
|
hi = hi if hi is not None else len(a)
|
|
pos = bisect.bisect_left(a, x, lo, hi)
|
|
return pos if pos != hi and a[pos] == x else -1
|
|
|
|
class Mnemonic(object):
|
|
def __init__(self):
|
|
self.language = "english"
|
|
self.radix = 2048
|
|
self.wordlist = bip39_wordlist
|
|
if len(self.wordlist) != self.radix:
|
|
raise ValueError("Wordlist should contain {} words, but it's {} words long instead.".format(self.radix, len(self.wordlist)))
|
|
|
|
@staticmethod
|
|
def normalize_string(txt):
|
|
if isinstance(txt, bytes):
|
|
utxt = txt.decode("utf8")
|
|
elif isinstance(txt, str):
|
|
utxt = txt
|
|
else:
|
|
raise TypeError("String value expected")
|
|
return unicodedata.normalize("NFKD", utxt)
|
|
|
|
def generate(self, strength=128):
|
|
if strength not in [128, 160, 192, 224, 256]:
|
|
raise ValueError("Invalid strength value. Allowed values are [128, 160, 192, 224, 256].")
|
|
return self.to_mnemonic(os.urandom(strength // 8))
|
|
|
|
def to_entropy(self, words):
|
|
if not isinstance(words, list):
|
|
words = words.split(" ")
|
|
if len(words) not in [12, 15, 18, 21, 24]:
|
|
raise ValueError("Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)."%len(words))
|
|
concatLenBits = len(words) * 11
|
|
concatBits = [False] * concatLenBits
|
|
wordindex = 0
|
|
if self.language == "english":
|
|
use_binary_search = True
|
|
else:
|
|
use_binary_search = False
|
|
for word in words:
|
|
ndx = (
|
|
binary_search(self.wordlist, word)
|
|
if use_binary_search
|
|
else self.wordlist.index(word)
|
|
)
|
|
if ndx < 0:
|
|
raise LookupError('Unable to find "%s" in word list.' % word)
|
|
for ii in range(11):
|
|
concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0
|
|
wordindex += 1
|
|
checksumLengthBits = concatLenBits // 33
|
|
entropyLengthBits = concatLenBits - checksumLengthBits
|
|
entropy = bytearray(entropyLengthBits // 8)
|
|
for ii in range(len(entropy)):
|
|
for jj in range(8):
|
|
if concatBits[(ii * 8) + jj]:
|
|
entropy[ii] |= 1 << (7 - jj)
|
|
hashBytes = hashlib.sha256(entropy).digest()
|
|
hashBits = list(
|
|
itertools.chain.from_iterable(
|
|
[c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes
|
|
)
|
|
)
|
|
for i in range(checksumLengthBits):
|
|
if concatBits[entropyLengthBits + i] != hashBits[i]:
|
|
raise ValueError("Failed checksum.")
|
|
return entropy
|
|
|
|
def to_mnemonic(self, data):
|
|
if len(data) not in [16, 20, 24, 28, 32]:
|
|
raise ValueError("Data length should be one of the following: [16, 20, 24, 28, 32], but is {}.".format(len(data)))
|
|
h = hashlib.sha256(data).hexdigest()
|
|
b = (
|
|
bin(int.from_bytes(data, byteorder="big"))[2:].zfill(len(data) * 8)
|
|
+ bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32]
|
|
)
|
|
result = []
|
|
for i in range(len(b) // 11):
|
|
idx = int(b[i * 11 : (i + 1) * 11], 2)
|
|
result.append(self.wordlist[idx])
|
|
if self.language == "japanese":
|
|
result_phrase = "\u3000".join(result)
|
|
else:
|
|
result_phrase = " ".join(result)
|
|
return result_phrase
|
|
|
|
def check(self, mnemonic):
|
|
mnemonic_list = self.normalize_string(mnemonic).split(" ")
|
|
if len(mnemonic_list) not in [12, 15, 18, 21, 24]:
|
|
return False
|
|
try:
|
|
idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic_list)
|
|
b = "".join(idx)
|
|
except ValueError:
|
|
return False
|
|
l = len(b) # noqa: E741
|
|
d = b[: l // 33 * 32]
|
|
h = b[-l // 33 :]
|
|
nd = int(d, 2).to_bytes(l // 33 * 4, byteorder="big")
|
|
nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33]
|
|
return h == nh
|
|
|
|
def expand_word(self, prefix):
|
|
if prefix in self.wordlist:
|
|
return prefix
|
|
else:
|
|
matches = [word for word in self.wordlist if word.startswith(prefix)]
|
|
if len(matches) == 1: # matched exactly one word in the wordlist
|
|
return matches[0]
|
|
else:
|
|
return prefix
|
|
|
|
def expand(self, mnemonic):
|
|
return " ".join(map(self.expand_word, mnemonic.split(" ")))
|
|
|
|
@classmethod
|
|
def to_seed(cls, mnemonic, passphrase = ""):
|
|
mnemonic = cls.normalize_string(mnemonic)
|
|
passphrase = cls.normalize_string(passphrase)
|
|
passphrase = "mnemonic" + passphrase
|
|
mnemonic_bytes = mnemonic.encode("utf-8")
|
|
passphrase_bytes = passphrase.encode("utf-8")
|
|
stretched = hashlib.pbkdf2_hmac("sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS)
|
|
return stretched[:64]
|
|
|
|
@classmethod
|
|
def mnemonic_to_xprv(cls, mnemonic, passphrase = ""):
|
|
seed = cls.to_seed(mnemonic, passphrase)
|
|
return Xpriv.from_seed(seed)
|
|
|
|
@staticmethod
|
|
def to_hd_master_key(seed, testnet = False):
|
|
if len(seed) != 64:
|
|
raise ValueError("Provided seed should have length of 64")
|
|
seed = hmac.new(b"Bitcoin seed", seed, digestmod=hashlib.sha512).digest()
|
|
xprv = b"\x04\x88\xad\xe4" # Version for private mainnet
|
|
if testnet:
|
|
xprv = b"\x04\x35\x83\x94" # Version for private testnet
|
|
xprv += b"\x00" * 9 # Depth, parent fingerprint, and child number
|
|
xprv += seed[32:] # Chain code
|
|
xprv += b"\x00" + seed[:32] # Master key
|
|
hashed_xprv = hashlib.sha256(xprv).digest()
|
|
hashed_xprv = hashlib.sha256(hashed_xprv).digest()
|
|
xprv += hashed_xprv[:4]
|
|
return b58encode(xprv)
|
|
|
|
bip39_mnemonic = Mnemonic()
|
|
|
|
def parse_ckd_path(str_path):
|
|
str_path = str_path.lstrip('m/')
|
|
path_split = []
|
|
for j in str_path.split('/'):
|
|
if not j:continue
|
|
hardened = 0
|
|
if j.endswith("'") or j.lower().endswith("h"):
|
|
hardened = 0x80000000
|
|
j = j[:-1]
|
|
try:
|
|
path_split.append([int(j)+hardened])
|
|
except:
|
|
a, b = map(int, j.split('-'))
|
|
path_split.append(list(range(a+hardened, b+1+hardened)))
|
|
return path_split
|
|
|
|
class Xpriv(collections.namedtuple('XprivNT', 'version depth prt_fpr childnr cc ktype key')):
|
|
xpriv_fmt = '>IB4sI32sB32s'
|
|
def __new__(cls, *a, **kw):
|
|
self = super(Xpriv, cls).__new__(cls, *a, **kw)
|
|
self.fullpath = 'm'
|
|
return self
|
|
@classmethod
|
|
def xpriv_version_bytes(cls):return 0x0488ADE4
|
|
@classmethod
|
|
def xpub_version_bytes(cls):return 0x0488B21E
|
|
@classmethod
|
|
def from_mnemomic(cls, mnemonic, passphrase = ""):
|
|
return bip39_mnemonic.mnemonic_to_xprv(mnemonic, passphrase)
|
|
@classmethod
|
|
def from_seed(cls, s, seed = b'Bitcoin seed'):
|
|
I = hmac.new(seed, s, digestmod=hashlib.sha512).digest()
|
|
mk, cc = I[:32], I[32:]
|
|
return cls(cls.xpriv_version_bytes(), 0, b'\x00'*4, 0, cc, 0, mk)
|
|
def clone(self):
|
|
return self.__class__.b58decode(self.b58encode())
|
|
def b58encode(self):
|
|
return EncodeBase58Check(struct.pack(self.xpriv_fmt, *self._asdict().values()))
|
|
def address(self, **kw):
|
|
return keyinfo(self, kw.get('network'), False, True).addr
|
|
def key_hex(self):
|
|
return binascii.hexlify(self.key)
|
|
def xpub(self, **kw):
|
|
pubk = keyinfo(self, None, False, True).public_key
|
|
xpub_content = self.clone()._replace(version=self.xpub_version_bytes(), ktype=ordsix(pubk[0]), key=pubk[1:])
|
|
return EncodeBase58Check(struct.pack(self.xpriv_fmt, *xpub_content))
|
|
@classmethod
|
|
def b58decode(cls, b58xpriv):
|
|
return cls(*struct.unpack(cls.xpriv_fmt, DecodeBase58Check(b58xpriv)))
|
|
def multi_ckd_xpriv(self, str_path):
|
|
path_split = parse_ckd_path(str_path)
|
|
rev_path_split = path_split[::-1]
|
|
xprivs = [self]
|
|
while rev_path_split:
|
|
children_nrs = rev_path_split.pop()
|
|
xprivs = [parent.ckd_xpriv(child_nr) for parent in xprivs for child_nr in children_nrs]
|
|
return xprivs
|
|
def set_fullpath(self, base, x):
|
|
self.fullpath = base + '/' + ("%d'"%(x-0x80000000) if x>=0x80000000 else "%d"%x)
|
|
return self
|
|
def ckd_xpriv(self, *indexes):
|
|
if indexes.__class__ != tuple:
|
|
indexes = [indexes]
|
|
if indexes[0].__class__ != int:
|
|
indexes = list(map(lambda x:x[0], parse_ckd_path(indexes[0])))
|
|
i = indexes[0]
|
|
if i<0:
|
|
i = 0x80000000-i
|
|
assert self.ktype == 0
|
|
par_pubk = keyinfo(self, None, False, True).public_key
|
|
seri = struct.pack('>I',i)
|
|
if i>=0x80000000:
|
|
I = hmac.new(self.cc, b'\x00'+self.key+seri, digestmod=hashlib.sha512).digest()
|
|
else:
|
|
I = hmac.new(self.cc, par_pubk+seri, digestmod=hashlib.sha512).digest()
|
|
il, ir = I[:32], I[32:]
|
|
pk = hex((int(binascii.hexlify(il), 16) + int(binascii.hexlify(self.key), 16))%_r)[2:].replace('L', '').zfill(64)
|
|
child = self.__class__(self.version, self.depth+1, hash_160(par_pubk)[:4], i, ir, 0, binascii.unhexlify(pk)).set_fullpath(self.fullpath, i)
|
|
if len(indexes)>=2:
|
|
return child.ckd_xpriv(*indexes[1:])
|
|
return child
|
|
def hprivcontent(self):
|
|
return binascii.hexlify(DecodeBase58Check(self.b58encode()))
|
|
def hpubcontent(self):
|
|
return binascii.hexlify(DecodeBase58Check(self.xpub()))
|
|
def keyinfo(self, network_name='Bitcoin'):
|
|
keyinfo(self, network_name, True, False)
|
|
print()
|
|
keyinfo(self, network_name, True, True)
|
|
|
|
def dump_bip32_privkeys(xpriv, paths='m/0', fmt='addr', **kw):
|
|
if fmt == 'addr':
|
|
dump_key = lambda x:x.addr
|
|
elif fmt == 'privkey':
|
|
dump_key = lambda x:bytes_to_str(binascii.hexlify(x.secret))
|
|
elif fmt == 'addrprivkey':
|
|
dump_key = lambda x:x.addr+' '+bytes_to_str(binascii.hexlify(x.secret))
|
|
elif fmt == 'addrwif':
|
|
dump_key = lambda x:x.addr+' '+x.wif
|
|
else:
|
|
dump_key = lambda x:x.wif
|
|
try:
|
|
xpriv = Xpriv.b58decode(xpriv)
|
|
except:pass
|
|
for child in xpriv.multi_ckd_xpriv(paths):
|
|
print('%s: %s'%(child.fullpath, dump_key(keyinfo(child, kw.get('network'), False, True))))
|
|
|
|
class TestPywallet(unittest.TestCase):
|
|
def setUp(self):
|
|
super(TestPywallet, self).setUp()
|
|
warnings.simplefilter('ignore')
|
|
def test_btc_privkey_1(self):
|
|
key = keyinfo('1', network=network_bitcoin, force_compressed=False)
|
|
self.assertEqual(key.addr, '1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm')
|
|
self.assertEqual(key.wif, '5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf')
|
|
self.assertEqual(key.secret, b'\x00'*31+b'\x01')
|
|
self.assertFalse(key.compressed)
|
|
def test_btc_privkey_1_from_wif(self):
|
|
key = keyinfo('5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf', network=network_bitcoin, force_compressed=False)
|
|
self.assertEqual(key.addr, '1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm')
|
|
def test_bad_privkey_format(self):
|
|
with self.assertRaises(Exception):
|
|
keyinfo('g', network=network_bitcoin)
|
|
def test_btc_bip32_test_vectors(self):
|
|
self.assertEqual(
|
|
Xpriv.from_seed(binascii.unhexlify('000102030405060708090a0b0c0d0e0f'))
|
|
.ckd_xpriv(0x80000000, 1, -2, 2, 1000000000).xpub(),
|
|
'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy'
|
|
)
|
|
self.assertEqual(
|
|
Xpriv.from_seed(binascii.unhexlify('fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542'))
|
|
.ckd_xpriv(0, -2147483647, 1, -2147483646, 2).xpub(),
|
|
'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt'
|
|
)
|
|
self.assertEqual(
|
|
Xpriv.from_seed(binascii.unhexlify('4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be'))
|
|
.ckd_xpriv(0x80000000).xpub(),
|
|
'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y'
|
|
)
|
|
def test_btc_bip32_2(self):
|
|
xpriv = "xprv9s21ZrQH143K2gCVXRarFj5npbgjtJ7MuNb15AoRYJ92ZMA1hcnoqpxJKfcsiMHP6cNmDKHCTphsC6uzzyzr2MwjXbDxg6U9ivvEupavYUb"
|
|
paths = "m/7-8'/3/99'/38-39"
|
|
keys = Xpriv.b58decode(xpriv).multi_ckd_xpriv(paths)
|
|
for k, privkey in zip(keys, [
|
|
'5ca736abd3b19632d11366c4dd79c227236500879980c6a1fc4e7c1e33933350',
|
|
'8c793bce5319bf04349b5e4d21d091a98c1a1ad632bffc0425a5f4802c999a76',
|
|
'692f2ddb1d5c7213d194643984642df6e9a5c8cd14a1a6b4054571955fcab05f',
|
|
'8739db9026ceb50d7774ef145bd27e899228700f1096072fe9d26f8387378314',
|
|
]):
|
|
self.assertEqual(k.key, binascii.unhexlify(privkey))
|
|
def test_btc_bip39_test_vectors(self):
|
|
self.assertEqual(
|
|
Xpriv.from_mnemomic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', 'TREZOR').b58encode(),
|
|
'xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF'
|
|
)
|
|
self.assertEqual(
|
|
Xpriv.from_mnemomic('void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold', 'TREZOR').b58encode(),
|
|
'xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS'
|
|
)
|
|
|
|
def test_btc_key_recovery(self):
|
|
pass
|
|
|
|
if __name__ == '__main__':
|
|
parser = OptionParser(usage="%prog [options]", version="%prog 1.1")
|
|
|
|
parser.add_option("--dump_bip32", nargs=2,
|
|
help="dump the keys from a xpriv and a path, usage: --dump_bip32 xprv9s21ZrQH143K m/0H/1-2/2H/2-4")
|
|
|
|
parser.add_option("--bip32_format",
|
|
help="format of dumped bip32 keys")
|
|
|
|
parser.add_option("--passphrase", dest="passphrase",
|
|
help="passphrase for the encrypted wallet")
|
|
|
|
parser.add_option("--find_address",
|
|
help="find info about an address")
|
|
|
|
parser.add_option("-d", "--dumpwallet", dest="dump", action="store_true",
|
|
help="dump wallet in json format")
|
|
|
|
parser.add_option("--dumpformat", default="all",
|
|
help="choose what to extract in a wallet dump")
|
|
|
|
parser.add_option("--dumpwithbalance", dest="dumpbalance", action="store_true",
|
|
help="includes balance of each address in the json dump, takes about 2 minutes per 100 addresses")
|
|
|
|
parser.add_option("--importprivkey", dest="key",
|
|
help="import private key from vanitygen")
|
|
|
|
parser.add_option("--importhex", dest="keyishex", action="store_true",
|
|
help="DEPRECATED, useless")
|
|
|
|
parser.add_option("--datadir", dest="datadir",
|
|
help="REMOVED OPTION: put full path in the --wallet option")
|
|
|
|
parser.add_option("-w", "--wallet", dest="walletfile",
|
|
help="wallet filename (defaults to wallet.dat)",
|
|
default="")
|
|
|
|
parser.add_option("--label", dest="label",
|
|
help="label shown in the adress book (defaults to '')",
|
|
default="")
|
|
|
|
parser.add_option("--testnet", dest="testnet", action="store_true",
|
|
help="use testnet subdirectory and address type")
|
|
|
|
parser.add_option("--namecoin", dest="namecoin", action="store_true",
|
|
help="use namecoin address type")
|
|
|
|
parser.add_option("--eth", dest="ethereum", action="store_true",
|
|
help="use ethereum address type")
|
|
|
|
parser.add_option("--otherversion", dest="otherversion",
|
|
help="use other network address type, either P2PKH prefix only (e.g. 111) or full network info as 'name,p2pkh,p2sh,wif,segwithrp' (e.g. btc,0,0,0x80,bc)")
|
|
|
|
parser.add_option("--info", dest="keyinfo", action="store_true",
|
|
help="display pubkey, privkey (both depending on the network) and hexkey")
|
|
|
|
parser.add_option("--reserve", dest="reserve", action="store_true",
|
|
help="import as a reserve key, i.e. it won't show in the adress book")
|
|
|
|
parser.add_option("--multidelete", dest="multidelete",
|
|
help="deletes data in your wallet, according to the file provided")
|
|
|
|
parser.add_option("--balance", dest="key_balance",
|
|
help="prints balance of KEY_BALANCE")
|
|
|
|
parser.add_option("--recover", dest="recover", action="store_true",
|
|
help="recover your deleted keys, use with recov_size and recov_device")
|
|
|
|
parser.add_option("--recov_device", dest="recov_device",
|
|
help="device to read (e.g. /dev/sda1 or E: or a file)")
|
|
|
|
parser.add_option("--recov_size", dest="recov_size",
|
|
help="number of bytes to read (e.g. 20Mo or 50Gio)")
|
|
|
|
parser.add_option("--recov_outputdir", dest="recov_outputdir",
|
|
help="output directory where the recovered wallet will be put")
|
|
|
|
parser.add_option("--clone_watchonly_from", dest="clone_watchonly_from",
|
|
help="path of the original wallet")
|
|
|
|
parser.add_option("--clone_watchonly_to", dest="clone_watchonly_to",
|
|
help="path of the resulting watch-only wallet")
|
|
|
|
parser.add_option("--dont_check_walletversion", dest="dcv", action="store_true",
|
|
help="don't check if wallet version > %d before running (WARNING: this may break your wallet, be sure you know what you do)"%max_version)
|
|
|
|
parser.add_option("--random_key", action="store_true",
|
|
help="print info of a randomly generated private key")
|
|
|
|
parser.add_option("--whitepaper", action="store_true",
|
|
help="write the Bitcoin whitepaper using bitcoin-cli or blockchain.info")
|
|
|
|
parser.add_option("--minimal_encrypted_copy", action="store_true",
|
|
help="write a copy of an encrypted wallet with only an empty address, *should* be safe to share when needing help bruteforcing the password")
|
|
|
|
parser.add_option("--tests", action="store_true",
|
|
help="run tests")
|
|
|
|
|
|
# parser.add_option("--forcerun", dest="forcerun",
|
|
# action="store_true",
|
|
# help="run even if pywallet detects bitcoin is running")
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
# a=Popen("ps xa | grep ' bitcoin'", shell=True, bufsize=-1, stdout=PIPE).stdout
|
|
# aread=a.read()
|
|
# nl = aread.count("\n")
|
|
# a.close()
|
|
# if nl > 2:
|
|
# print('Bitcoin seems to be running: \n"%s"'%(aread))
|
|
# if options.forcerun is None:
|
|
# exit(0)
|
|
|
|
if options.tests:
|
|
unittest.main(argv=sys.argv[:1] + ['TestPywallet'])
|
|
exit()
|
|
|
|
if options.dump_bip32:
|
|
print("Warning: single quotes (') may be parsed by your terminal, please use \"H\" for hardened keys")
|
|
dump_bip32_privkeys(*options.dump_bip32, format=options.bip32_format)
|
|
exit()
|
|
|
|
if options.whitepaper:
|
|
whitepaper()
|
|
exit()
|
|
|
|
if options.passphrase:
|
|
passphrase = options.passphrase
|
|
|
|
if not(options.clone_watchonly_from is None) and options.clone_watchonly_to:
|
|
clone_wallet(options.clone_watchonly_from, options.clone_watchonly_to)
|
|
exit(0)
|
|
|
|
|
|
if options.recover:
|
|
if options.recov_size is None or options.recov_device is None or options.recov_outputdir is None:
|
|
print("You must provide the device, the number of bytes to read and the output directory")
|
|
exit(0)
|
|
device = options.recov_device
|
|
if len(device) in [2,3] and device[1]==':':
|
|
device="\\\\.\\"+device
|
|
size = read_device_size(options.recov_size)
|
|
|
|
passphraseRecov=None
|
|
while not passphraseRecov:
|
|
passphraseRecov=getpass.getpass("Enter the passphrase for the wallet that will contain all the recovered keys%s: "%('' if passphraseRecov is None else " (can't be empty)"))
|
|
passphrase=passphraseRecov
|
|
|
|
passes=[]
|
|
p=' '
|
|
print('\nEnter the possible passphrases used in your deleted wallets.')
|
|
print("Don't forget that more passphrases = more time to test the possibilities.")
|
|
print('Write one passphrase per line and end with an empty line.')
|
|
while p!='':
|
|
p=getpass.getpass("Possible passphrase: ")
|
|
if p!='':
|
|
passes.append(p)
|
|
|
|
print("\nStarting recovery.")
|
|
recoveredKeys=recov(device, passes, size, 10240, options.recov_outputdir)
|
|
recoveredKeys=list(set(recoveredKeys))
|
|
# print(recoveredKeys[0:5])
|
|
|
|
|
|
db_env = create_env(options.recov_outputdir)
|
|
recov_wallet_name = "recovered_wallet_%s.dat"%ts()
|
|
|
|
create_new_wallet(db_env, recov_wallet_name, 32500)
|
|
|
|
if passphraseRecov!="I don't want to put a password on the recovered wallet and I know what can be the consequences.":
|
|
db = open_wallet(db_env, recov_wallet_name, True)
|
|
|
|
NPP_salt=os.urandom(8)
|
|
NPP_rounds=int(50000+random.random()*20000)
|
|
NPP_method=0
|
|
NPP_MK=os.urandom(32)
|
|
crypter.SetKeyFromPassphrase(passphraseRecov, NPP_salt, NPP_rounds, NPP_method)
|
|
NPP_EMK = crypter.Encrypt(NPP_MK)
|
|
update_wallet(db, 'mkey', {
|
|
"encrypted_key": NPP_EMK,
|
|
'nDerivationIterations' : NPP_rounds,
|
|
'nDerivationMethod' : NPP_method,
|
|
'nID' : 1,
|
|
'otherParams' : b'',
|
|
"salt": NPP_salt
|
|
})
|
|
db.close()
|
|
|
|
read_wallet(json_db, db_env, recov_wallet_name, True, True, "", False)
|
|
|
|
db = open_wallet(db_env, recov_wallet_name, True)
|
|
|
|
print("\n\nImporting:")
|
|
for i,sec in enumerate(recoveredKeys):
|
|
sec=binascii.hexlify(sec)
|
|
print("Importing key %4d/%d"%(i+1, len(recoveredKeys)))
|
|
importprivkey(db, sec, "recovered: %s"%sec, None, False)
|
|
importprivkey(db, sec+'01', "recovered: %s"%sec, None, False)
|
|
db.close()
|
|
|
|
print("\n\nThe new wallet %s/%s contains the %d recovered key%s"%(options.recov_outputdir, recov_wallet_name, len(recoveredKeys), plural(len(recoveredKeys))))
|
|
|
|
exit(0)
|
|
|
|
|
|
if 'bsddb' in missing_dep:
|
|
print("pywallet needs 'bsddb' package to run, please install it")
|
|
exit(0)
|
|
|
|
if 'ecdsa' in missing_dep:
|
|
print("Warning: 'ecdsa' package is not installed, so you won't be able to sign/verify messages but everything else will work fine")
|
|
|
|
if not(options.dcv is None):
|
|
max_version = 10 ** 9
|
|
|
|
if not(options.datadir is None):
|
|
print("Depreacation")
|
|
print(" The --datadir option has been deprecated, now the full path of the wallet file should go to --wallet")
|
|
print(" If you're not sure what to do, concatenating the old --datadir content, then a directory separator, then the old --wallet should do the trick")
|
|
print(" If not, ask for help in the Pywallet thread: https://bitcointalk.org/index.php?topic=34028")
|
|
|
|
db_dir = ""
|
|
if options.walletfile:
|
|
if options.datadir:options.walletfile=options.datadir+os.path.sep+options.walletfile
|
|
if not os.path.isfile(options.walletfile):
|
|
print("ERROR: wallet file %s can't be found"%repr(os.path.realpath(options.walletfile)))
|
|
exit()
|
|
db_dir, wallet_name = os.path.split(os.path.realpath(options.walletfile))
|
|
|
|
if not(options.key_balance is None):
|
|
print(balance(balance_site, options.key_balance))
|
|
exit(0)
|
|
|
|
network = network_bitcoin
|
|
if not(options.otherversion is None):
|
|
try:
|
|
network = find_network(options.otherversion)
|
|
if not network:
|
|
network = Network('Unknown network', int(options.otherversion), None, None, None)
|
|
print("Some network info is missing: please use the complete network format")
|
|
except:
|
|
network_info = options.otherversion.split(',')
|
|
parse_int=lambda x:int(x, 16) if x.startswith('0x') else int(x)
|
|
network = Network(network_info[0], parse_int(network_info[1]), parse_int(network_info[2]), parse_int(network_info[3]), network_info[4])
|
|
if options.namecoin:
|
|
network = Network('Namecoin', 52, 13, 180, 'nc')
|
|
elif options.testnet:
|
|
db_dir += "/testnet3"
|
|
network = network_bitcoin_testnet3
|
|
elif options.ethereum:
|
|
network = network_ethereum
|
|
|
|
if not(options.keyinfo is None) or options.random_key:
|
|
if not options.keyinfo:
|
|
options.key = binascii.hexlify(os.urandom(32))
|
|
keyinfo(options.key, network, True, False)
|
|
print("")
|
|
keyinfo(options.key, network, True, True)
|
|
exit(0)
|
|
|
|
if not db_dir:
|
|
print("A mandatory option is missing\n")
|
|
parser.print_help()
|
|
exit()
|
|
db_env = create_env(db_dir)
|
|
|
|
if not(options.multidelete is None):
|
|
filename=options.multidelete
|
|
filin = open(filename, 'r')
|
|
content = filin.read().split('\n')
|
|
filin.close()
|
|
typedel=content[0]
|
|
kd=filter(bool,content[1:])
|
|
try:
|
|
r=delete_from_wallet(db_env, wallet_name, typedel, kd)
|
|
print('%d element%s deleted'%(r, 's'*(int(r>1))))
|
|
except:
|
|
print("Error: do not try to delete a non-existing transaction.")
|
|
exit(1)
|
|
exit(0)
|
|
|
|
if options.minimal_encrypted_copy:
|
|
db = open_wallet(db_env, wallet_name)
|
|
minimal_wallet = wallet_name + '.minimal_for_decrypting.dat'
|
|
assert not os.path.exists(os.path.join(db_dir, minimal_wallet)), "There is already a minimal encrypted copy at %s/%s, exiting"%(db_dir, minimal_wallet)
|
|
kds = BCDataStream()
|
|
vds = BCDataStream()
|
|
encrypted_keys = []
|
|
mkey = None
|
|
for (key, value) in db.items():
|
|
d = Bdict({})
|
|
kds.clear(); kds.write(key)
|
|
vds.clear(); vds.write(value)
|
|
typ = kds.read_string()
|
|
if typ == b'mkey':
|
|
mkey = (key, value)
|
|
if typ != b'ckey':continue
|
|
d['public_key'] = kds.read_bytes(kds.read_compact_size())
|
|
d['__key__'] = key
|
|
d['__value__'] = value
|
|
encrypted_keys.append(d)
|
|
db.close()
|
|
print('''
|
|
Before creating a safe partial wallet you need to check the balance of the following addresses.
|
|
You may check the balance on your wallet or using an online block explorer.
|
|
Just hit Enter if the address is empty and write 'no' if not empty.
|
|
|
|
''')
|
|
for pbk in encrypted_keys[::-1]:
|
|
p2pkh, p2wpkh, witaddr, _ = pubkey_info(pbk['public_key'], network)
|
|
for addr in [p2pkh, p2wpkh, witaddr]:
|
|
has_balance = raw_input(addr + ': ') != ''
|
|
if has_balance:
|
|
print('')
|
|
break
|
|
if not has_balance:
|
|
if raw_input("\nAre you REALLY sure the 3 addresses above have an empty balance? (type 'YES') ") == 'YES':
|
|
output_db = open_wallet(db_env, minimal_wallet, True)
|
|
output_db.put(*mkey)
|
|
output_db.put(pbk['__key__'], pbk['__value__'])
|
|
output_db.close()
|
|
print('\nMinimal wallet written at %s'%minimal_wallet)
|
|
exit()
|
|
else:
|
|
print('\nYou need to input zero character only when the balance is empty, exiting')
|
|
exit()
|
|
print("\nError: all your addresses seem to be used, pywallet can't create a safe minimal wallet to share")
|
|
exit()
|
|
|
|
read_wallet(json_db, db_env, wallet_name, True, True, "", not(options.dumpbalance is None))
|
|
|
|
if json_db.get('minversion', 99999999) > max_version:
|
|
print("Version mismatch (must be <= %d)" % max_version)
|
|
#exit(1)
|
|
|
|
if options.find_address:
|
|
addr_data = filter(lambda x:x["addr"] == options.find_address, json_db["keys"]+json_db["pool"])
|
|
print(json.dumps(list(addr_data), sort_keys=True, indent=4))
|
|
exit()
|
|
|
|
if options.dump:
|
|
if options.dumpformat == 'addr':
|
|
addrs = list(map(lambda x:x["addr"], json_db["keys"]+json_db["pool"]))
|
|
json_db = addrs
|
|
wallet = json.dumps(json_db, sort_keys=True, indent=4)
|
|
print(wallet)
|
|
exit()
|
|
elif options.key:
|
|
if json_db['version'] > max_version:
|
|
print("Version mismatch (must be <= %d)" % max_version)
|
|
elif options.key in private_keys or options.key in private_hex_keys:
|
|
print("Already exists")
|
|
else:
|
|
db = open_wallet(db_env, wallet_name, writable=True)
|
|
|
|
if importprivkey(db, options.key, options.label, options.reserve):
|
|
print("Imported successfully")
|
|
else:
|
|
print("Bad private key")
|
|
|
|
db.close()
|
|
exit()
|
|
|
|
|
|
|
|
|
|
|