diff --git a/pywallet.py b/pywallet.py index 703debb..4d88b5c 100755 --- a/pywallet.py +++ b/pywallet.py @@ -1,8 +1,17 @@ #!/usr/bin/env python +pywversion="2.0b8-bt" +never_update=False -# pywallet.py 1.1 -# based on http://github.com/gavinandresen/bitcointools # +# jackjack's pywallet.py +# https://github.com/jackjack-jj/pywallet +# forked from Joric's pywallet.py +# + + + + +beta_version = ('a' in pywversion.split('-')[0]) or ('b' in pywversion.split('-')[0]) missing_dep = [] @@ -12,6 +21,9 @@ except: missing_dep.append('bsddb') import os, sys, time +pyw_filename = sys.argv[0].split('/')[len(sys.argv[0].split('/'))-1] +pyw_path = os.getcwd() + try: for i in os.listdir('/usr/lib/python2.5/site-packages'): if 'Twisted' in i: @@ -20,7 +32,7 @@ except: '' try: - import json + import json except: try: import simplejson as json @@ -38,6 +50,7 @@ import exceptions import hashlib import random import urllib +import math try: from twisted.internet import reactor @@ -51,11 +64,14 @@ from datetime import datetime from subprocess import * -max_version = 32500 +max_version = 81000 addrtype = 0 json_db = {} private_keys = [] private_hex_keys = [] +passphrase = "" +global_merging_message = ["",""] + balance_site = 'http://bitcoin.site50.net/balance.php?adresse' aversions = {}; for i in range(256): @@ -76,7 +92,7 @@ Gio = 1024 ** 3 To = 1e12 Tio = 1024 ** 4 -prekeys = ["82011302010104".decode('hex'), "82011202010104".decode('hex')] +prekeys = ["308201130201010420".decode('hex'), "308201120201010420".decode('hex')] postkeys = ["a081a530".decode('hex'), "81a530".decode('hex')] def iais(a): @@ -104,6 +120,717 @@ def determine_db_name(): else: return wallet_name +######################## +# 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*chr(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 = ord(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] = ord(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 = '' + # 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 += chr(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 += chr(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 originalsize is not None and originalsize < end: + for k in range(originalsize-start): + stringOut += chr(plaintext[k]) + else: + for k in range(end-start): + stringOut += chr(plaintext[k]) + iput = ciphertext + return stringOut + +###################### +# end of aes.py code # +###################### + +################################### +# pywallet crypter implementation # +################################### + +crypter = None + +try: + from Crypto.Cipher import AES + crypter = 'pycrypto' +except: + pass + +class Crypter_pycrypto( object ): + def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): + if nDerivationMethod != 0: + return 0 + data = 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(data)[0:32] + + def Decrypt(self, data): + return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32] + +try: + if not crypter: + import ctypes + import ctypes.util + ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32') + crypter = 'ssl' +except: + pass + +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 = 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 = [ord(i) for i in key] + + def SetIV(self, iv): + self.chIV = [ord(i) for i in iv] + + def Encrypt(self, data): + mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV) + return ''.join(map(chr, cypher)) + + def Decrypt(self, data): + chData = [ord(i) for i in data] + return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV) + +if crypter == 'pycrypto': + crypter = Crypter_pycrypto() +elif crypter == 'ssl': + crypter = Crypter_ssl() +else: + crypter = Crypter_pure() + logging.warning("pycrypto or libssl not found, decryption may be slow") + +########################################## +# end of pywallet crypter implementation # +########################################## + # secp256k1 _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL @@ -124,6 +851,8 @@ try: except: missing_dep.append('ecdsa') +# python-ecdsa code (EC_KEY implementation) + class CurveFp( object ): def __init__( self, p, a, b ): self.__p = p @@ -300,35 +1029,56 @@ class EC_KEY(object): self.privkey = Private_key( self.pubkey, secret ) self.secret = secret -def i2d_ECPrivateKey(pkey): - # private keys are 279 bytes long (see crypto/ec/cec_asn1.c) - # ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG), - # ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING), - # ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0), - # ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1) - hex_i2d_key = '308201130201010420' + \ - '%064x' % pkey.secret + \ - 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ - '%064x' % _p + \ - '3006040100040107044104' + \ - '%064x' % _Gx + \ - '%064x' % _Gy + \ - '022100' + \ - '%064x' % _r + \ - '020101a14403420004' + \ - '%064x' % pkey.pubkey.point.x() + \ - '%064x' % pkey.pubkey.point.y() - return hex_i2d_key.decode('hex') +# end of python-ecdsa code -def i2o_ECPublicKey(pkey): +# 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 key.decode('hex') + 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 - hex_i2o_key = '04' + \ - '%064x' % pkey.pubkey.point.x() + \ - '%064x' % pkey.pubkey.point.y() - return hex_i2o_key.decode('hex') + # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed + # compressed keys: where 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() -# hashes + return key.decode('hex') + +# bitcointools hashes and base58 implementation def hash_160(public_key): md = hashlib.new('ripemd160') @@ -401,26 +1151,30 @@ def b58decode(v, length): return result +# end of bitcointools base58 implementation + +# address handling code + def long_hex(bytes): return bytes.encode('hex_codec') def Hash(data): return hashlib.sha256(hashlib.sha256(data).digest()).digest() -def EncodeBase58Check(vchIn): - hash = Hash(vchIn) - return b58encode(vchIn + hash[0:4]) +def EncodeBase58Check(secret): + hash = Hash(secret) + return b58encode(secret + hash[0:4]) -def DecodeBase58Check(psz): - vchRet = b58decode(psz, None) - key = vchRet[0:-4] +def DecodeBase58Check(sec): + vchRet = b58decode(sec, None) + secret = vchRet[0:-4] csum = vchRet[-4:] - hash = Hash(key) + hash = Hash(secret) cs32 = hash[0:4] if cs32 != csum: return None else: - return key + return secret def str_to_long(b): res = 0 @@ -431,15 +1185,19 @@ def str_to_long(b): return res def PrivKeyToSecret(privkey): - return privkey[9:9+32] + if len(privkey) == 279: + return privkey[9:9+32] + else: + return privkey[8:8+32] -def SecretToASecret(secret): - vchIn = chr(addrtype+128) + secret +def SecretToASecret(secret, compressed=False): + vchIn = chr((addrtype+128)&255) + secret + if compressed: vchIn += '\01' return EncodeBase58Check(vchIn) -def ASecretToSecret(key): - vch = DecodeBase58Check(key) - if vch and vch[0] == chr(addrtype+128): +def ASecretToSecret(sec): + vch = DecodeBase58Check(sec) + if vch and vch[0] == chr((addrtype+128)&255): return vch[1:] else: return False @@ -448,19 +1206,24 @@ def regenerate_key(sec): b = ASecretToSecret(sec) if not b: return False - secret = str_to_long(b) + b = b[0:32] + secret = int('0x' + b.encode('hex'), 16) return EC_KEY(secret) -def GetPubKey(pkey): - return i2o_ECPublicKey(pkey) +def GetPubKey(pkey, compressed=False): + return i2o_ECPublicKey(pkey, compressed) -def GetPrivKey(pkey): - return i2d_ECPrivateKey(pkey) +def GetPrivKey(pkey, compressed=False): + return i2d_ECPrivateKey(pkey, compressed) def GetSecret(pkey): return ('%064x' % pkey.secret).decode('hex') -# parser +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) @@ -664,6 +1427,18 @@ def read_device_size(size): exec 'unit = %s' % unit return int(value * unit) +def md5_2(a): + return hashlib.md5(a).digest() + +def md5_file(nf): + fichier = file(nf, 'r').read()[:-1] + return md5_2(fichier) + +def md5_onlinefile(add): + page = urllib.urlopen(add).read() + return md5_2(page) + + class KEY: def __init__ (self): @@ -704,13 +1479,13 @@ class KEY: encoded_gxgy = "\x04" + ("%64x" % _Gx).decode('hex') + ("%64x" % _Gy).decode('hex') param_sequence = der.encode_sequence ( ecdsa.der.encode_integer(1), - der.encode_sequence ( - encoded_oid2, - der.encode_integer (_p), + 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("\x00"), + der.encode_octet_string("\x07"), ), der.encode_octet_string (encoded_gxgy), der.encode_integer (_r), @@ -930,6 +1705,8 @@ def parse_wallet(db, item_callback): d['name'] = vds.read_string() elif type == "version": d['version'] = vds.read_uint32() + elif type == "minversion": + d['minversion'] = vds.read_uint32() elif type == "setting": d['setting'] = kds.read_string() d['value'] = parse_setting(d['setting'], vds) @@ -964,12 +1741,23 @@ def parse_wallet(db, item_callback): elif type == "bestblock": d['nVersion'] = vds.read_int32() d.update(parse_BlockLocator(vds)) + elif type == "ckey": + d['public_key'] = kds.read_bytes(kds.read_compact_size()) + d['encrypted_private_key'] = vds.read_bytes(vds.read_compact_size()) + elif type == "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, e: traceback.print_exc() print("ERROR parsing wallet.dat, type %s" % type) + print("key data: %s"%key) print("key data in hex: %s"%key.encode('hex_codec')) print("value data in hex: %s"%value.encode('hex_codec')) sys.exit(1) @@ -1010,6 +1798,117 @@ def delete_from_wallet(db_env, walletfile, typedel, keydel): db.close() return deleted_items +def merge_keys_lists(la, lb): + lr={} + 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=random_string(16).decode('hex') + NPP_rounds=int(50000+random.random()*20000) + NPP_method=0 + NPP_MK=random_string(64).decode('hex') + + 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' : ''.decode('hex'), + "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, type, data): """Write a single item to the wallet. db must be open with writable=True. @@ -1036,6 +1935,8 @@ def update_wallet(db, type, data): vds.write_string(d['name']) elif type == "version": vds.write_uint32(d['version']) + elif type == "minversion": + vds.write_uint32(d['minversion']) elif type == "setting": raise NotImplementedError("Writing items of type 'setting'") kds.write_string(d['setting']) @@ -1073,6 +1974,17 @@ def update_wallet(db, type, data): vds.write_compact_size(len(d['hashes'])) for h in d['hashes']: vds.write(h) + elif type == "ckey": + kds.write_string(d['public_key']) + vds.write_string(d['encrypted_private_key']) + elif type == "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: "+type @@ -1122,7 +2034,14 @@ def rewrite_wallet(db_env, walletfile, destFileName, pre_put_callback=None): db_out.close() db.close() -def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transactions, transaction_filter, include_balance, vers=-1): +# end of bitcointools wallet.dat handling code + +# wallet.dat reader / writer + +def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transactions, transaction_filter, include_balance, vers=-1, FillPool=False): + global passphrase + crypted=False + private_keys = [] private_hex_keys = [] @@ -1131,12 +2050,14 @@ def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transact oldaddrtype = addrtype addrtype = vers - db = open_wallet(db_env, walletfile) + db = open_wallet(db_env, walletfile, writable=FillPool) json_db['keys'] = [] json_db['pool'] = [] json_db['tx'] = [] json_db['names'] = {} + json_db['ckey'] = [] + json_db['mkey'] = {} def item_callback(type, d): if type == "tx": @@ -1148,6 +2069,9 @@ def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transact elif type == "version": json_db['version'] = d['version'] + elif type == "minversion": + json_db['minversion'] = d['minversion'] + elif type == "setting": if not json_db.has_key('settings'): json_db['settings'] = {} json_db["settings"][d['setting']] = d['value'] @@ -1157,19 +2081,25 @@ def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transact elif type == "key": addr = public_key_to_bc_address(d['public_key']) - secret = PrivKeyToSecret(d['private_key']) - hexsec = secret.encode('hex') - sec = SecretToASecret(secret) + compressed = d['public_key'][0] != '\04' + sec = SecretToASecret(PrivKeyToSecret(d['private_key']), compressed) + hexsec = ASecretToSecret(sec).encode('hex') private_keys.append(sec) - private_hex_keys.append(hexsec) - json_db['keys'].append({'addr' : addr, 'sec' : sec, 'hexsec' : hexsec}) + json_db['keys'].append({'addr' : addr, 'sec' : sec, 'hexsec' : hexsec, 'secret' : hexsec, 'pubkey':d['public_key'].encode('hex'), 'compressed':compressed, 'private':d['private_key'].encode('hex')}) elif type == "wkey": if not json_db.has_key('wkey'): json_db['wkey'] = [] json_db['wkey']['created'] = d['created'] elif type == "pool": - json_db['pool'].append( {'n': d['n'], 'addr': public_key_to_bc_address(d['public_key']), 'nTime' : d['nTime'] } ) + """ 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(d['public_key'].decode('hex')), 'addr3': public_key_to_bc_address(d['public_key'].encode('hex')), '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' : d['public_key'].encode('hex') } ) elif type == "acc": json_db['acc'] = d['account'] @@ -1181,12 +2111,34 @@ def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transact elif type == "bestblock": json_db['bestblock'] = d['hashes'][0][::-1].encode('hex_codec') + elif type == "ckey": + crypted=True + compressed = d['public_key'][0] != '\04' + json_db['keys'].append({ 'pubkey': d['public_key'].encode('hex'),'addr': public_key_to_bc_address(d['public_key']), 'encrypted_privkey': d['encrypted_private_key'].encode('hex_codec'), 'compressed':compressed}) + + elif type == "mkey": + json_db['mkey']['nID'] = d['nID'] + json_db['mkey']['encrypted_key'] = d['encrypted_key'].encode('hex_codec') + json_db['mkey']['salt'] = d['salt'].encode('hex_codec') + 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' + print d + list_of_reserve_not_in_pool=[] parse_wallet(db, item_callback) - db.close() nkeys = len(json_db['keys']) i = 0 @@ -1194,44 +2146,135 @@ def read_wallet(json_db, db_env, walletfile, print_wallet, print_wallet_transact i+=1 addr = k['addr'] if include_balance is not None: -# print("%3d/%d %s" % (i, nkeys, k["addr"])) +# 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' : p.decode('hex'), 'n' : cpt, 'nTime' : ts(), 'nVersion':80100 }) + cpt+=1 + + + + db.close() + + crypted = 'salt' in json_db['mkey'] + + if not crypted: + print "The wallet is not crypted" + + if crypted and not passphrase: + print "The wallet is crypted but no passphrase is used" + + if crypted and passphrase: + check = True + for k in json_db['keys']: + if 'encrypted_privkey' in k: + ckey = k['encrypted_privkey'].decode('hex') + public_key = k['pubkey'].decode('hex') + crypter.SetIV(Hash(public_key)) + secret = crypter.Decrypt(ckey) + compressed = public_key[0] != '\04' + + + if check: + check = False + pkey = EC_KEY(int('0x' + secret.encode('hex'), 16)) + if public_key != GetPubKey(pkey, compressed): + print "The wallet is crypted and the passphrase is incorrect" + break + + sec = SecretToASecret(secret, compressed) + k['sec'] = sec + k['secret'] = secret.encode('hex') + k['compressed'] = compressed +# del(k['ckey']) +# del(k['secret']) +# del(k['pubkey']) + private_keys.append(sec) + print "The wallet is crypted and the passphrase is correct" + + for k in json_db['keys']: + if k['compressed'] and 'secret' in k: + k['secret']+="01" + # del(json_db['pool']) # del(json_db['names']) if vers > -1: addrtype = oldaddrtype + return {'crypted':crypted} -def importprivkey(db, sec, label, reserve, keyishex): + + +def importprivkey(db, sec, label, reserve, keyishex, verbose=True): if keyishex is None: pkey = regenerate_key(sec) + compressed = is_compressed(sec) elif len(sec) == 64: pkey = EC_KEY(str_to_long(sec.decode('hex'))) + compressed = False + elif len(sec) == 66: + pkey = EC_KEY(str_to_long(sec[:-2].decode('hex'))) + compressed = True else: - print("Hexadecimal private keys must be 64 characters long") - exit(0) + print("Hexadecimal private keys must be 64 or 66 characters long (specified one is "+str(len(sec))+" characters long)") + return False if not pkey: return False secret = GetSecret(pkey) - private_key = GetPrivKey(pkey) - public_key = GetPubKey(pkey) + private_key = GetPrivKey(pkey, compressed) + public_key = GetPubKey(pkey, compressed) addr = public_key_to_bc_address(public_key) - print "Address: %s" % addr - print "Privkey: %s" % SecretToASecret(secret) + if verbose: + print "Address: %s" % addr + print "Privkey: %s" % SecretToASecret(secret, compressed) + + + global crypter, passphrase, json_db + crypted = 'salt' in json_db['mkey'] + if crypted: + if passphrase: + cry_master = json_db['mkey']['encrypted_key'].decode('hex') + cry_salt = json_db['mkey']['salt'].decode('hex') + 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, "", cry_master.encode('hex'), "", cry_salt.encode('hex') + 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 }) - 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): @@ -1258,32 +2301,40 @@ def write_jsonfile(filename, array): def keyinfo(sec, keyishex): if keyishex is None: pkey = regenerate_key(sec) + compressed = is_compressed(sec) elif len(sec) == 64: pkey = EC_KEY(str_to_long(sec.decode('hex'))) + compressed = False + elif len(sec) == 66: + pkey = EC_KEY(str_to_long(sec[:-2].decode('hex'))) + compressed = True else: - print("Hexadecimal private keys must be 64 characters long") + print("Hexadecimal private keys must be 64 or 66 characters long (specified one is "+str(len(sec))+" characters long)") exit(0) if not pkey: return False secret = GetSecret(pkey) - private_key = GetPrivKey(pkey) - public_key = GetPubKey(pkey) + private_key = GetPrivKey(pkey, compressed) + public_key = GetPubKey(pkey, compressed) addr = public_key_to_bc_address(public_key) print "Address (%s): %s" % ( aversions[addrtype], addr ) - print "Privkey (%s): %s" % ( aversions[addrtype], SecretToASecret(secret) ) - print "Hexprivkey: %s" % secret.encode('hex') + print "Privkey (%s): %s" % ( aversions[addrtype], SecretToASecret(secret, compressed) ) + print "Hexprivkey: %s" % secret.encode('hex') return True -def WI_FormInit(title, action): - return '

%s

'%(title, action) +def WI_FormInit(title, action, divname): + return '

  '+title+'

' def WI_InputText(label, name, id, value, size=30): return '%s
'%(label, name, id, value, size) +def WI_InputPassword(label, name, id, value, size=30): + return '%s
'%(label, name, id, value, size) + def WI_Submit(value, local_block, local_button, function): return ''%(value, local_block, local_button, function) @@ -1294,13 +2345,13 @@ def WI_ReturnDiv(local_block): return ''%(local_block) def WI_FormEnd(): - return '
' + return '
' def WI_RadioButton(name, value, id, checked, label): return '   %s
'%(name, value, id, checked, label) def WI_Checkbox(name, value, id, other, label): - return '%s
'%(name, value, id, other, label) + return '%s'%(name, value, id, other, label) def WI_AjaxFunction(name, command_when_ready, query_string, command_until_ready): return '\n\ @@ -1338,13 +2389,109 @@ def X_if_else(iftrue, cond, iffalse): return iftrue return iffalse +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]!="#": + sec,label=c.split(';') + reserve=False + if label=="#Reserve": + reserve=True + keyishex=False + if abs(len(sec)-65)==1: + keyishex=True + importprivkey(db, sec, label, reserve, keyishex, verbose=False) + + global_merging_message = ["Merging done.", ""] + + db.close() + + read_wallet(json_db, db_env, wname, True, True, "", None, -1, True) #Fill the pool if empty + + return True + if 'twisted' not in missing_dep: class WIRoot(resource.Resource): def render_GET(self, request): - header = '

Pywallet Web Interface

CLOSE BITCOIN BEFORE USE!



' + try: + request.args['update'][0] + update_pyw() + except: + True - DWForm = WI_FormInit('Dump your wallet:', 'DumpWallet') + \ + uptodate=md5_last_pywallet[1]==md5_pywallet + uptodate=not uptodate + checking_finished=bool(md5_last_pywallet[0]) + + checking_finished,uptodate=[True,False] + color="#DDDDFF" + if checking_finished: + if uptodate: + color="#DDFFDD" + else: + color="#FFDDDD" + + check_version_text = \ + X_if_else( + X_if_else( + 'Pywallet is up-to-date', + uptodate, + 'Pywallet is NOT up-to-date
Click to update, then restart Pywallet'), + checking_finished, + 'Checking version...' + ) + + if beta_version: + check_version_text="You are using a beta version
Thank you for your help" + color="#DDDDDD" + + global pywversion + header = '

Pywallet Web Interface v'+pywversion+'

CLOSE BITCOIN BEFORE USE!

' + check_version_text + '


' + + CPPForm = WI_FormInit('Change passphrase:', 'ChangePP', 'divformcpp') + \ + WI_InputPassword('', 'pp', 'cppf-pp', '') + \ + WI_Submit('Change', 'CPPDiv', 'cppf-close', 'ajaxCPP') + \ + WI_CloseButton('CPPDiv', 'cppf-close') + \ + WI_ReturnDiv('CPPDiv') + \ + WI_FormEnd() + + DWForm = WI_FormInit('Dump your wallet:', 'DumpWallet', 'divformdw') + \ WI_InputText('Wallet Directory: ', 'dir', 'dwf-dir', determine_db_dir()) + \ WI_InputText('Wallet Filename: ', 'name', 'dwf-name', determine_db_name(), 20) + \ WI_InputText('Version:', 'vers', 'dwf-vers', '0', 1) + \ @@ -1353,7 +2500,43 @@ if 'twisted' not in missing_dep: WI_ReturnDiv('DWDiv') + \ WI_FormEnd() - DTxForm = WI_FormInit('Dump your transactions to a file:', 'DumpTx') + \ + MWForm = WI_FormInit('Merge two wallets:', 'MergeWallets', 'divformmw') + \ + WI_InputText('Wallet 1 Directory: ', 'dir1', 'mwf-dir1', determine_db_dir()) + \ + WI_InputText('Wallet 1 Filename: ', 'name1', 'mwf-name1', determine_db_name(), 20) + \ + WI_InputPassword('Wallet 1 Passphrase: ', 'pass1', 'mwf-pass1', '') + "
" + \ + WI_InputText('Wallet 2 Directory: ', 'dir2', 'mwf-dir2', determine_db_dir()) + \ + WI_InputText('Wallet 2 Filename: ', 'name2', 'mwf-name2', "", 20) + \ + WI_InputPassword('Wallet 2 Passphrase: ', 'pass2', 'mwf-pass2', '') + "
" + \ + WI_InputText('Merged Wallet Directory: ', 'dirm', 'mwf-dirm', determine_db_dir()) + \ + WI_InputText('Merged Wallet Filename: ', 'namem', 'mwf-namem', "", 20) + \ + WI_InputPassword('Merged Wallet Passphrase: ', 'passm1', 'mwf-passm1', '') + \ + WI_InputPassword('Repeat Wallet Passphrase: ', 'passm2', 'mwf-passm2', '') + \ + WI_Submit('Merge wallets', 'MWDiv', 'mwf-close', 'ajaxMW') + \ + WI_CloseButton('MWDiv', 'mwf-close') + \ + WI_ReturnDiv('MWDiv') + \ + WI_FormEnd() + + DKForm = WI_FormInit('Dump your keys:', 'DumpKeys', 'divformdk') + \ + WI_InputText('Wallet Directory: ', 'dir', 'dkf-dir', determine_db_dir()) + \ + WI_InputText('Wallet Filename: ', 'name', 'dkf-name', determine_db_name(), 20) + \ + WI_InputText('Version:', 'vers', 'dkf-vers', '0', 1) + \ + WI_InputText('Output file: ', 'file', 'dkf-file', '', 60) + \ + WI_InputText('Data to print: ', 'keys', 'dkf-keys', '') + \ + WI_Submit('Dump keys', 'DKDiv', 'dkf-close', 'ajaxDK') + \ + WI_CloseButton('DKDiv', 'dkf-close') + \ + WI_ReturnDiv('DKDiv') + \ + WI_FormEnd() + + IKForm = WI_FormInit('Import keys:', 'ImportKeys', 'divformik') + \ + WI_InputText('Wallet Directory: ', 'dir', 'ikf-dir', determine_db_dir()) + \ + WI_InputText('Wallet Filename: ', 'name', 'ikf-name', determine_db_name(), 20) + \ + WI_InputText('CSV file path: ', 'file', 'ikf-file', '', 60) + \ + WI_Submit('Import keys', 'IKDiv', 'ikf-close', 'ajaxIK') + \ + WI_CloseButton('IKDiv', 'ikf-close') + \ + WI_ReturnDiv('IKDiv') + \ + WI_FormEnd() + + DTxForm = WI_FormInit('Dump your transactions to a file:', 'DumpTx', 'divformdtx') + \ WI_InputText('Wallet Directory: ', 'dir', 'dt-dir', determine_db_dir()) + \ WI_InputText('Wallet Filename: ', 'name', 'dt-name', determine_db_name(), 20) + \ WI_InputText('Output file: ', 'file', 'dt-file', '') + \ @@ -1362,42 +2545,50 @@ if 'twisted' not in missing_dep: WI_ReturnDiv('DTxDiv') + \ WI_FormEnd() - InfoForm = WI_FormInit('Get some info about one key'+X_if_else(' and sign/verify messages', 'ecdsa' not in missing_dep,'')+':', 'Info') + \ + prehide_ecdsa="" + posthide_ecdsa="" + if 'ecdsa' in missing_dep: + prehide_ecdsa="" + posthide_ecdsa="" + InfoForm = WI_FormInit('Get some info about one key'+X_if_else(' and sign/verify messages', 'ecdsa' not in missing_dep,'')+':', 'Info', 'divforminfo') + \ WI_InputText('Key: ', 'key', 'if-key', '', 60) + \ - X_if_else(WI_InputText('Message: ', 'msg', 'if-msg', '', 30), 'ecdsa' not in missing_dep, '') + \ - X_if_else(WI_InputText('Signature: ', 'sig', 'if-sig', '', 30), 'ecdsa' not in missing_dep, '') + \ - X_if_else(WI_InputText('Pubkey: ', 'pubkey', 'if-pubkey', '', 30), 'ecdsa' not in missing_dep, '') + \ + prehide_ecdsa + WI_InputText('Message: ', 'msg', 'if-msg', '', 30) + posthide_ecdsa + \ + prehide_ecdsa + WI_InputText('Signature: ', 'sig', 'if-sig', '', 30) + posthide_ecdsa + \ + prehide_ecdsa + WI_InputText('Pubkey: ', 'pubkey', 'if-pubkey', '', 30) + posthide_ecdsa + \ WI_InputText('Version:', 'vers', 'if-vers', '0', 1) + \ "Format:
" + \ WI_RadioButton('format', 'reg', 'if-reg', 'CHECKED', ' Regular, base 58') + \ WI_RadioButton('format', 'hex', 'if-hex', '', ' Hexadecimal, 64 characters long') + \ "You want:
" + \ - WI_RadioButton('i-need', '1', 'if-n-info', '', ' Info') + \ - WI_RadioButton('i-need', '2', 'if-n-sv', '', ' Sign and verify') + \ - WI_RadioButton('i-need', '3', 'if-n-both', 'CHECKED', ' Both') + \ + WI_RadioButton('i-need', '1', 'if-n-info', 'CHECKED', ' Info') + \ + prehide_ecdsa + WI_RadioButton('i-need', '2', 'if-n-sv', '', ' Sign and verify') + posthide_ecdsa +\ + prehide_ecdsa + WI_RadioButton('i-need', '3', 'if-n-both', '', ' Both') + posthide_ecdsa + \ WI_Submit('Get info', 'InfoDiv', 'if-close', 'ajaxInfo') + \ WI_CloseButton('InfoDiv', 'if-close') + \ WI_ReturnDiv('InfoDiv') + \ WI_FormEnd() - ImportForm = WI_FormInit('Import a key into your wallet:', 'Import') + \ + ImportForm = WI_FormInit('Import a key into your wallet:', 'Import', 'divformimport') + \ WI_InputText('Wallet Directory: ', 'dir', 'impf-dir', determine_db_dir(), 30) + \ WI_InputText('Wallet Filename:', 'name', 'impf-name', determine_db_name(), 20) + \ WI_InputText('Key:', 'key', 'impf-key', '', 65) + \ WI_InputText('Label:', 'label', 'impf-label', '') + \ - WI_Checkbox('reserve', 'true', 'impf-reserve', 'onClick="document.getElementById(\'impf-label\').disabled=document.getElementById(\'impf-reserve\').checked"', ' Reserve') + \ + WI_Checkbox('reserve', 'true', 'impf-reserve', 'onClick="document.getElementById(\'impf-label\').disabled=document.getElementById(\'impf-reserve\').checked"', ' Reserve') + "
" + \ WI_InputText('Version:', 'vers', 'impf-vers', '0', 1) + \ "Format:
" + \ - WI_RadioButton('format', 'reg', 'impf-reg', 'CHECKED', ' Regular, base 58') + \ - WI_RadioButton('format', 'hex', 'impf-hex', '', ' Hexadecimal, 64 characters long') + \ + WI_Checkbox('format', 'hex', 'impf-hex', '', ' Hexadecimal, instead of base58') + "
" + \ + WI_Checkbox('format', 'cry', 'impf-cry', 'hidden=true', '') + \ + WI_Checkbox('format', 'com', 'impf-com', 'hidden=true', '') + \ WI_Submit('Import key', 'ImportDiv', 'impf-close', 'ajaxImport') + \ WI_CloseButton('ImportDiv', 'impf-close') + \ WI_ReturnDiv('ImportDiv') + \ WI_FormEnd() +# WI_RadioButton('format', 'reg', 'impf-reg', 'CHECKED', ' Regular, base 58') + \ +# WI_RadioButton('format', 'hex', 'impf-hex', '', ' Hexadecimal, 64 characters long') + \ - DeleteForm = WI_FormInit('Delete a key from your wallet:', 'Delete') + \ + DeleteForm = WI_FormInit('Delete a key from your wallet:', 'Delete', 'divformdelete') + \ WI_InputText('Wallet Directory: ', 'dir', 'd-dir', determine_db_dir(), 40) + \ WI_InputText('Wallet Filename:', 'name', 'd-name', determine_db_name()) + \ WI_InputText('Key:', 'key', 'd-key', '', 65) + \ @@ -1409,7 +2600,7 @@ if 'twisted' not in missing_dep: WI_ReturnDiv('DeleteDiv') + \ WI_FormEnd() - ImportTxForm = WI_FormInit('Import a transaction into your wallet:', 'ImportTx') + \ + ImportTxForm = WI_FormInit('Import a transaction into your wallet:', 'ImportTx', 'divformimporttx') + \ WI_InputText('Wallet Directory: ', 'dir', 'it-dir', determine_db_dir(), 40) + \ WI_InputText('Wallet Filename:', 'name', 'it-name', determine_db_name()) + \ WI_InputText('Txk:', 'key', 'it-txk', '', 65) + \ @@ -1419,14 +2610,22 @@ if 'twisted' not in missing_dep: WI_ReturnDiv('ImportTxDiv') + \ WI_FormEnd() - BalanceForm = WI_FormInit('Print the balance of a Bitcoin address:', 'Balance') + \ + BalanceForm = WI_FormInit('Print the balance of a Bitcoin address:', 'Balance', 'divformbalance') + \ WI_InputText('Key:', 'key', 'bf-key', '', 35) + \ '
' + \ WI_FormEnd() Misc = '' - Javascript = '' - page = 'Pywallet Web Interface' + header + Javascript + DWForm + DTxForm + InfoForm + ImportForm + ImportTxForm + DeleteForm + BalanceForm + Misc + '' +# WI_AjaxFunction('Import', 'document.getElementById("ImportDiv").innerHTML = ajaxRequest.responseText;', '"/Import?dir="+document.getElementById("impf-dir").value+"&name="+document.getElementById("impf-name").value+"&key="+document.getElementById("impf-key").value+"&label="+document.getElementById("impf-label").value+"&vers="+document.getElementById("impf-vers").value+"&format="+(document.getElementById("impf-hex").checked?"hex":"reg")+(document.getElementById("impf-reserve").checked?"&reserve=1":"")', 'document.getElementById("ImportDiv").innerHTML = "Loading...";') + \ + + + page = 'Pywallet Web Interface' + header + Javascript + CPPForm + DWForm + MWForm + DKForm + IKForm + DTxForm + InfoForm + ImportForm + ImportTxForm + DeleteForm + BalanceForm + Misc + '' return page def getChild(self, name, request): - if name == '': - return self - else: - if name in VIEWS.keys(): - return resource.Resource.getChild(self, name, request) - else: - return WI404() + if name == '': + return self + else: + if name in VIEWS.keys(): + return resource.Resource.getChild(self, name, request) + else: + return WI404() class WIDumpWallet(resource.Resource): def render_GET(self, request): - try: + try: + wdir=request.args['dir'][0] wname=request.args['name'][0] version = int(request.args['version'][0]) @@ -1471,18 +2679,29 @@ if 'twisted' not in missing_dep: return '%s/%s doesn\'t exist'%(wdir, wname) read_wallet(json_db, create_env(wdir), wname, True, True, "", None, version) - return 'Wallet: %s/%s
Dump:
%s
'%(wdir, wname, json.dumps(json_db, sort_keys=True, indent=4)) - except: - log.err() - return 'Error in dump page' +# print wdir +# print wname +# print json_db #json.dumps(json_db, sort_keys=True, indent=4) - def render_POST(self, request): - return self.render_GET(request) + try: + kfile=request.args['filetw'][0] + kkeys=request.args['keys'][0] +# print kkeys.split(',') + reteak=export_all_keys(json_db, kkeys.split(','), kfile) + return 'File'+X_if_else("",reteak," not")+' written' + except: + return 'Wallet: %s/%s
Dump:
%s
'%(wdir, wname, json.dumps(json_db, sort_keys=True, indent=4)) + except: + log.err() + return 'Error in dump page' + + def render_POST(self, request): + return self.render_GET(request) class WIDumpTx(resource.Resource): def render_GET(self, request): - try: + try: wdir=request.args['dir'][0] wname=request.args['name'][0] jsonfile=request.args['file'][0] @@ -1497,29 +2716,95 @@ if 'twisted' not in missing_dep: read_wallet(json_db, create_env(wdir), wname, True, True, "", None) write_jsonfile(jsonfile, json_db['tx']) return 'Wallet: %s/%s
Transations dumped in %s'%(wdir, wname, jsonfile) - except: - log.err() - return 'Error in dumptx page' + except: + log.err() + return 'Error in dumptx page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) + + class WIMergeWallets(resource.Resource): + + def render_GET(self, request): + try: + dir1=request.args['dir1'][0] + name1=request.args['name1'][0] + pass1=request.args['pass1'][0] + dir2=request.args['dir2'][0] + name2=request.args['name2'][0] + pass2=request.args['pass2'][0] + dirm=request.args['dirm'][0] + namem=request.args['namem'][0] + passm1=request.args['passm1'][0] + passm2=request.args['passm2'][0] + if passm1!=passm2: + return 'The passphrases for the merged wallet don\'t match. Aborted.' + + r=merge_wallets(dir1, name1, dir2, name2, dirm, namem, pass1, pass2, passm1) + if r[0]: + return "Merging in progress..." + else: + return r[1] + + return ret + except: + log.err() + return 'Error in mergewallets page' + + def render_POST(self, request): + return self.render_GET(request) + + class WIChangePP(resource.Resource): + + def render_GET(self, request): + try: + global passphrase + passphrase=request.args['pp'][0] + return 'Done' + except: + log.err() + return 'Error while changing passphrase' + + def render_POST(self, request): + return self.render_GET(request) + + class WIOthers(resource.Resource): + + def render_GET(self, request): + try: + global passphrase + global global_merging_message + + action=request.args['action'][0] + if action=="update_mwdiv": + ret=global_merging_message[0] + global_merging_message[0]=global_merging_message[1] + global_merging_message[1]="" + return ret + return 'Done' + except: + log.err() + return 'Error while WIOthers' + + def render_POST(self, request): + return self.render_GET(request) class WIBalance(resource.Resource): def render_GET(self, request): - try: + try: return "%s"%str(balance(balance_site, request.args['key'][0]).encode('utf-8')) - except: - log.err() - return 'Error in balance page' + except: + log.err() + return 'Error in balance page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WIDelete(resource.Resource): def render_GET(self, request): - try: + try: wdir=request.args['dir'][0] wname=request.args['name'][0] keydel=request.args['keydel'][0] @@ -1533,12 +2818,12 @@ if 'twisted' not in missing_dep: return "%s:%s has been successfully deleted from %s/%s, resulting in %d deleted item%s"%(typedel, keydel, wdir, wname, deleted_items, iais(deleted_items)) - except: - log.err() - return 'Error in delete page' + except: + log.err() + return 'Error in delete page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) def message_to_hash(msg, msgIsHex=False): str = "" @@ -1639,8 +2924,8 @@ if 'twisted' not in missing_dep: class WIInfo(resource.Resource): def render_GET(self, request): - global addrtype - try: + global addrtype + try: sec = request.args['key'][0] format = request.args['format'][0] addrtype = int(request.args['vers'][0]) @@ -1671,8 +2956,13 @@ if 'twisted' not in missing_dep: if sec is not '': if format in 'reg': pkey = regenerate_key(sec) + compressed = is_compressed(sec) elif len(sec) == 64: pkey = EC_KEY(str_to_long(sec.decode('hex'))) + compressed = False + elif len(sec) == 66: + pkey = EC_KEY(str_to_long(sec[:-2].decode('hex'))) + compressed = True else: return "Hexadecimal private keys must be 64 characters long" @@ -1709,19 +2999,19 @@ if 'twisted' not in missing_dep: return ret - except: - log.err() - return 'Error in info page' + except: + log.err() + return 'Error in info page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WIImportTx(resource.Resource): def render_GET(self, request): - global addrtype - try: + global addrtype + try: wdir=request.args['dir'][0] wname=request.args['name'][0] txk=request.args['txk'][0] @@ -1754,32 +3044,49 @@ if 'twisted' not in missing_dep: return "
hash: %s\n%d transaction%s imported in %s/%s
" % (inverse_str(txk[6:]), i, iais(i), wdir, wname)
 
-		     except:
-		         log.err()
-		         return 'Error in importtx page'
+			 except:
+				 log.err()
+				 return 'Error in importtx page'
 
-		     def render_POST(self, request):
-		         return self.render_GET(request)
+			 def render_POST(self, request):
+				 return self.render_GET(request)
 
 	class WIImport(resource.Resource):
 
 		 def render_GET(self, request):
-		     global addrtype
-		     try:
-					sec = request.args['key'][0]
-					format = request.args['format'][0]
-					addrtype = int(request.args['vers'][0])
+			 global addrtype
+			 try:
+
 					wdir=request.args['dir'][0]
 					wname=request.args['name'][0]
-					reserve=request.args.has_key('reserve')
-					label=request.args['label'][0]
+
+					try:		#Import a single key
+						addrtype = int(request.args['vers'][0])
+						format = X_if_else('hex', request.args['format'][0]=='true', 'reg')
+						reserve=request.args.has_key('reserve')
+						label=request.args['label'][0]
+						compressed=request.args['com'][0]=='true'
+						tocrypt=request.args['cry'][0]=='true'
+						sec = request.args['key'][0]
+					except:		#Import csv file
+						ret=import_csv_keys(request.args['file'][0],wdir,wname)
+						return "File "+X_if_else("", ret, "not ")+"imported"
+
+
 				
+
+
 					if format in 'reg':
 						pkey = regenerate_key(sec)
+						compressed = is_compressed(sec)
 					elif len(sec) == 64:
 						pkey = EC_KEY(str_to_long(sec.decode('hex')))
+						compressed = False
+					elif len(sec) == 66:
+						pkey = EC_KEY(str_to_long(sec[:-2].decode('hex')))
+						compressed = True
 					else:
-						return "Hexadecimal private keys must be 64 characters long"
+						return "Hexadecimal private keys must be 64 or 66 characters long"
 
 					if not pkey:
 						return "Bad private key"
@@ -1788,50 +3095,98 @@ if 'twisted' not in missing_dep:
 						return '%s/%s doesn\'t exist'%(wdir, wname)
 
 
+					db_env = create_env(wdir)
+					ret_read = read_wallet(json_db, db_env, wname, True, True, "", None)
+					tocrypt = ret_read['crypted']
+					db = open_wallet(db_env, wname, writable=True)
+
+
 					secret = GetSecret(pkey)
-					private_key = GetPrivKey(pkey)
-					public_key = GetPubKey(pkey)
+					private_key = GetPrivKey(pkey, compressed)
+					public_key = GetPubKey(pkey, compressed)
 					addr = public_key_to_bc_address(public_key)
 
-					db_env = create_env(wdir)
-					read_wallet(json_db, db_env, wname, True, True, "", None)
-					db = open_wallet(db_env, wname, writable=True)
+
 
 					if (format in 'reg' and sec in private_keys) or (format not in 'reg' and sec in private_hex_keys):
 						return "Already exists"
 
-					update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key })
+					if not tocrypt:
+						update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key })
+					else:
+						cry_master = json_db['mkey']['encrypted_key'].decode('hex')
+						cry_salt   = json_db['mkey']['salt'].decode('hex')
+						cry_rounds = json_db['mkey']['nDerivationIterations']
+						cry_method = json_db['mkey']['nDerivationMethod']
+
+						crypter.SetKeyFromPassphrase(passphrase, cry_salt, cry_rounds, cry_method)
+						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 })
+
 					if not reserve:
 						update_wallet(db, 'name', { 'hash' : addr, 'name' : label })
 	
 					db.close()
 
-					return "
Address: %s\nPrivkey: %s\nHexkey: %s\nKey imported in %s/%s
" % (addr, SecretToASecret(secret), secret.encode('hex'), wdir, wname)
+					return "
Address: %s\nPrivkey: %s\nHexkey: %s\nKey (%scrypted, %scompressed) imported in %s/%s
" % (addr, SecretToASecret(secret, compressed), secret.encode('hex'), X_if_else("",tocrypt,"un"), X_if_else("",compressed,"un"), wdir, wname)
 
-		     except:
-		         log.err()
-		         return 'Error in import page'
+			 except:
+				 log.err()
+				 return 'Error in import page'
 
-		     def render_POST(self, request):
-		         return self.render_GET(request)
+			 def render_POST(self, request):
+				 return self.render_GET(request)
 
 	class WI404(resource.Resource):
 
 		 def render_GET(self, request):
-		     return 'Page Not Found'
+			 return 'Page Not Found'
 
 
+def update_pyw():
+	if md5_last_pywallet[0] and md5_last_pywallet[1] not in md5_pywallet:
+		filout = open(pyw_filename, 'w')
+		filout.write(urllib.urlopen('https://raw.github.com/jackjack-jj/pywallet/master/pywallet.py').read())
+		filout.close()
+		reactor.stop()
+
+
+
+import thread
+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')]
+
+def zzz(a):
+	global pywversion
+	pywversion=a
+
 from optparse import OptionParser
 
 if __name__ == '__main__':
 
+	md5_pywallet = md5_file(pyw_filename)
+	thread.start_new_thread(retrieve_last_pywallet_md5, ())
+
+
+	
 	parser = OptionParser(usage="%prog [options]", version="%prog 1.1")
 
+	parser.add_option("--passphrase", dest="passphrase",
+		help="passphrase for the encrypted wallet")
+
 	parser.add_option("--dumpwallet", dest="dump", action="store_true",
 		help="dump wallet in json format")
 
-#	parser.add_option("--dumpwithbalance", dest="dumpbalance", action="store_true",
-#		help="includes balance of each address in the json dump, can take a *LONG* time and might experience timeouts or bans")
+	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")
@@ -1889,6 +3244,8 @@ if __name__ == '__main__':
 	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("--forcerun", dest="forcerun",
 #		action="store_true",
 #		help="run even if pywallet detects bitcoin is running")
@@ -1903,6 +3260,10 @@ if __name__ == '__main__':
 #		print('Bitcoin seems to be running: \n"%s"'%(aread))
 #		if options.forcerun is None:
 #			exit(0)
+	global passphrase
+
+	if options.passphrase:
+		passphrase = options.passphrase
 
 	if options.recover:
 		if options.recov_size is None or options.recov_device is None or options.recov_outputdir is None:
@@ -1948,12 +3309,15 @@ if __name__ == '__main__':
 	if 'twisted' not in missing_dep:
 		VIEWS = {
 			 'DumpWallet': WIDumpWallet(),
+			 'MergeWallets': WIMergeWallets(),
 			 'Import': WIImport(),
 			 'ImportTx': WIImportTx(),
 			 'DumpTx': WIDumpTx(),
 			 'Info': WIInfo(),
 			 'Delete': WIDelete(),
-			 'Balance': WIBalance()
+			 'Balance': WIBalance(),
+			 'ChangePP': WIChangePP(),
+			 'Others': WIOthers()
 		}
 
 	if options.dcv is not None:
@@ -2012,7 +3376,11 @@ if __name__ == '__main__':
 
 	db_env = create_env(db_dir)
 
-	read_wallet(json_db, db_env, determine_db_name(), True, True, "", None)
+	read_wallet(json_db, db_env, determine_db_name(), True, True, "", options.dumpbalance is not None)
+
+	if json_db.get('minversion') > max_version:
+		print "Version mismatch (must be <= %d)" % max_version
+		#exit(1)
 
 	if options.dump:		
 		print json.dumps(json_db, sort_keys=True, indent=4)