Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
6 contributors

Users who have contributed to this file

@jackjack-jj @joric @neldredge @Krellan @gurnec @dgnthr
5049 lines (4178 sloc) 337 KB
#!/usr/bin/env python
#-*- coding: utf-8 -*-
pywversion="2.2"
never_update=False
#
# 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 = []
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:
for i in os.listdir('/usr/lib/python2.5/site-packages'):
if 'Twisted' in i:
sys.path.append('/usr/lib/python2.5/site-packages/'+i)
except:
''
try:
import json
except:
try:
import simplejson as json
except:
print("Json or simplejson package is needed")
import logging
import struct
import StringIO
import traceback
import socket
import types
import string
import exceptions
import hashlib
import random
import urllib
import math
try:
from twisted.internet import reactor
from twisted.web import server, resource
from twisted.web.static import File
from twisted.python import log
except:
missing_dep.append('twisted')
from datetime import datetime
from subprocess import *
import os
import os.path
import platform
max_version = 81000
addrtype = 0
json_db = {}
private_keys = []
private_hex_keys = []
passphrase = ""
global_merging_message = ["",""]
balance_site = 'https://blockchain.info/q/addressbalance/'
aversions = {};
for i in range(256):
aversions[i] = "version %d" % i;
aversions[0] = 'Bitcoin';
aversions[48] = 'Litecoin';
aversions[52] = 'Namecoin';
aversions[111] = 'Testnet';
wallet_dir = ""
wallet_name = ""
ko = 1e3
kio = 1024
Mo = 1e6
Mio = 1024 ** 2
Go = 1e9
Gio = 1024 ** 3
To = 1e12
Tio = 1024 ** 4
prekeys = ["308201130201010420".decode('hex'), "308201120201010420".decode('hex')]
postkeys = ["a081a530".decode('hex'), "81a530".decode('hex')]
def iais(a):
if a>= 2:
return 's'
else:
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
########################
# 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(append_PKCS7_padding(data))
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(append_PKCS7_padding(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()
# print "Crypter: pycrypto"
elif crypter == 'ssl':
crypter = Crypter_ssl()
# print "Crypter: ssl"
else:
crypter = Crypter_pure()
# print "Crypter: pure"
logging.warning("pycrypto or libssl not found, decryption may be slow")
##########################################
# end of pywallet crypter implementation #
##########################################
# secp256k1
try:
_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
except:
print "Python 3 is not supported, you need Python 2.7.x"
exit(1)
_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
_b = 0x0000000000000000000000000000000000000000000000000000000000000007L
_a = 0x0000000000000000000000000000000000000000000000000000000000000000L
_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
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
class Point( object ):
def __init__( self, curve, x, y, order = None ):
self.__curve = curve
self.__x = x
self.__y = y
self.__order = order
if self.__curve: assert self.__curve.contains_point( x, 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 = 1L
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 )
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 pk.decode('hex')
def get_addr(self, v=0):
return public_key_to_bc_address(self.ser(), v)
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 hex_der_key.decode('hex')
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 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
# 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 key.decode('hex')
# 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=addrtype
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=addrtype
vh160 = chr(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'
__b58base = len(__b58chars)
def b58encode(v):
""" encode v, which is a string of bytes, to base58.
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += (256**i) * ord(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 c == '\0': nPad += 1
else: break
return (__b58chars[0]*nPad) + result
def b58decode(v, length):
""" decode v into a string of len bytes
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += __b58chars.find(c) * (__b58base**i)
result = ''
while long_value >= 256:
div, mod = divmod(long_value, 256)
result = chr(mod) + result
long_value = div
result = chr(long_value) + result
nPad = 0
for c in v:
if c == __b58chars[0]: nPad += 1
else: break
result = chr(0)*nPad + result
if length is not None and len(result) != length:
return None
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(secret):
hash = Hash(secret)
return b58encode(secret + hash[0:4])
def DecodeBase58Check(sec):
vchRet = b58decode(sec, None)
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 += ord(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 = chr((addrtype+128)&255)
if addrtype==48: #assuming Litecoin
prefix = chr(128)
vchIn = prefix + secret
if compressed: vchIn += '\01'
return EncodeBase58Check(vchIn)
def ASecretToSecret(sec):
vch = DecodeBase58Check(sec)
if not vch:
return False
if vch[0] != chr((addrtype+128)&255):
print 'Warning: adress prefix seems bad (%d vs %d)'%(ord(vch[0]), (addrtype+128)&255)
return vch[1:]
def regenerate_key(sec):
b = ASecretToSecret(sec)
if not b:
return False
b = b[0:32]
secret = int('0x' + b.encode('hex'), 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 ('%064x' % pkey.secret).decode('hex')
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 = {'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 = { '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: "+d['hashes'][0][::-1].encode('hex_codec')
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 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:", e.args
exit(0)
i = 0
data=''
tzero=time.time()
sizetokeep=0
BlocksToInspect=dict(map(lambda x:[x,[]], patternlist))
syst=systype()
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:[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[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:]!='':
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
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]]!=x[1].decode('hex')), 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]]!=x[1].decode('hex')), checks)): #number of false statements
return None
return me
def recov_uckey(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]]!=x[1].decode('hex')), checks)): #number of false statements
return None
return me
def starts_with(s, b):
return len(s)>=len(b) and s[:len(b)]==b
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':'\x09\x00\x01\x04mkey','ckey':'\x27\x00\x01\x04ckey','key':'\x00\x01\x03key',}
if not starts_with(device, 'PartialRecoveryFile:'):
r=search_patterns_on_disk(device, size, inc, map(lambda x:nameToDBName[x], ['mkey', 'ckey', 'key']))
f=open(outputdir+'/pywallet_partial_recovery_%d.dat'%ts(), 'w')
f.write(str(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()
cmd=("z = "+content+"")
exec cmd in locals()
r=z
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=[]
syst=systype()
for offset in r[nameToDBName['mkey']]:
s=recov_mkey(fd, offset)
if s==None:
continue
newmkey=RecovMkey(s[1],s[3],int(s[5][::-1].encode('hex'), 16),int(s[4][::-1].encode('hex'), 16),int(s[-1][::-1].encode('hex'), 16))
mkeys.append([offset,newmkey])
print "Found", len(mkeys), 'possible wallets'
ckeys=[]
for offset in r[nameToDBName['ckey']]:
s=recov_ckey(fd, offset)
if s==None:
continue
newckey=RecovCkey(s[1], s[5][:int(s[4].encode('hex'),16)])
ckeys.append([offset,newckey])
print "Found", len(ckeys), 'possible encrypted keys'
uckeys=[]
for offset in r[nameToDBName['key']]:
s=recov_uckey(fd, offset)
if s==None:
continue
uckeys.append(s[4])
print "Found", len(uckeys), 'possible unencrypted keys'
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('0x' + secret.encode('hex'), 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=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('0x' + secret.encode('hex'), 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 = ""
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 = ""
for j in range(len(prekey)):
buf += "\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 = data[1:33].encode('hex')
after_key = data[33:39].encode('hex')
if hexkey not in found_hexkeys and check_postkeys(after_key.decode('hex'), postkeys):
found_hexkeys.append(hexkey)
os.close (fd)
return found_hexkeys
def read_device_size(size):
if size[-2] == 'i':
unit = size[-3:]
value = float(size[:-3])
else:
unit = size[-2:]
value = float(size[:-2])
exec 'unit = %s' % unit
return int(value * unit)
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 ('0x' + secret.encode ('hex'), 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 = "\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 (
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 sig.encode('hex')
def verify (self, hash, sig):
return self.pubkey.verify_digest (sig, hash, sigdecode=ecdsa.util.sigdecode_der)
def bool_to_int(b):
if b:
return 1
return 0
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 ''
def read_boolean(self): return self.read_bytes(1)[0] != chr(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(chr(bool_to_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 = ord(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(chr(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:
r = True
if r is not None:
logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.")
sys.exit(1)
return db
def inversetxid(txid):
if len(txid) is not 64:
print("Bad txid")
return "CORRUPTEDTXID:"+txid
# exit(0)
new_txid = ""
for i in range(32):
new_txid += txid[62-2*i];
new_txid += txid[62-2*i+1];
return new_txid
def parse_wallet(db, item_callback):
kds = BCDataStream()
vds = BCDataStream()
def parse_TxIn(vds):
d = {}
d['prevout_hash'] = vds.read_bytes(32).encode('hex')
d['prevout_n'] = vds.read_uint32()
d['scriptSig'] = vds.read_bytes(vds.read_compact_size()).encode('hex')
d['sequence'] = vds.read_uint32()
return d
def parse_TxOut(vds):
d = {}
d['value'] = vds.read_int64()/1e8
d['scriptPubKey'] = vds.read_bytes(vds.read_compact_size()).encode('hex')
return d
for (key, value) in db.items():
d = { }
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 == "tx":
d["tx_id"] = inversetxid(kds.read_bytes(32).encode('hex_codec'))
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'] = vds.input[start:vds.read_cursor].encode('hex_codec')
d['txv'] = value.encode('hex_codec')
d['txk'] = key.encode('hex_codec')
elif type == "name":
d['hash'] = kds.read_string()
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)
elif type == "key":
d['public_key'] = kds.read_bytes(kds.read_compact_size())
d['private_key'] = vds.read_bytes(vds.read_compact_size())
elif type == "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 == "defaultkey":
d['key'] = vds.read_bytes(vds.read_compact_size())
elif type == "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 == "acc":
d['account'] = kds.read_string()
d['nVersion'] = vds.read_int32()
d['public_key'] = vds.read_bytes(vds.read_compact_size())
elif type == "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 == "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)
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'+keydel.decode('hex')[::-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 == "tx":
db.delete(key)
deleted_items+=1
elif typedel == "key":
if type == "key" or type == "ckey":
if keydel == public_key_to_bc_address(kds.read_bytes(kds.read_compact_size())):
db.delete(key)
deleted_items+=1
elif type == "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 == "name":
if keydel == kds.read_string():
db.delete(key)
deleted_items+=1
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=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' : ''.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, 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):
data=datas[it]
d = data
kds = BCDataStream()
vds = BCDataStream()
# Write the type code to the key
kds.write_string(type)
vds.write("") # Ensure there is something
try:
if type == "tx":
# raise NotImplementedError("Writing items of type 'tx'")
kds.write(d['txi'][6:].decode('hex_codec'))
vds.write(d['txv'].decode('hex_codec'))
elif type == "name":
kds.write_string(d['hash'])
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'])
#d['value'] = parse_setting(d['setting'], vds)
elif type == "key":
kds.write_string(d['public_key'])
vds.write_string(d['private_key'])
elif type == "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 == "defaultkey":
vds.write_string(d['key'])
elif type == "pool":
kds.write_int64(d['n'])
vds.write_int32(d['nVersion'])
vds.write_int64(d['nTime'])
vds.write_string(d['public_key'])
elif type == "acc":
kds.write_string(d['account'])
vds.write_int32(d['nVersion'])
vds.write_string(d['public_key'])
elif type == "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 == "bestblock":
vds.write_int32(d['nVersion'])
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
# Write the key/value pair to the database
db.put(kds.input, vds.input)
except Exception, 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 r is not None:
logging.error("Couldn't open %s."%walletfile)
sys.exit(1)
db_out.put("0776657273696f6e".decode('hex'), ("%08x"%version).decode('hex')[::-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 r is not 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, vers=-1, FillPool=False):
global passphrase, addr_to_keys
crypted=False
private_keys = []
private_hex_keys = []
if vers > -1:
global addrtype
oldaddrtype = addrtype
addrtype = vers
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":
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 == "name":
json_db['names'][d['hash']] = d['name']
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']
elif type == "defaultkey":
json_db['defaultkey'] = public_key_to_bc_address(d['key'])
elif type == "key":
addr = public_key_to_bc_address(d['public_key'])
compressed = d['public_key'][0] != '\04'
sec = SecretToASecret(PrivKeyToSecret(d['private_key']), compressed)
hexsec = ASecretToSecret(sec)[:32].encode('hex')
private_keys.append(sec)
addr_to_keys[addr]=[hexsec, d['public_key'].encode('hex')]
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":
""" 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']
print("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key'])))
elif type == "acentry":
json_db['acentry'] = (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])
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 "Wallet data not recognized: "+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' : 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 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 = 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 encrypted and the passphrase is incorrect"
ppcorrect=False
break
sec = SecretToASecret(secret, compressed)
k['sec'] = sec
k['hexsec'] = secret[:32].encode('hex')
k['secret'] = secret.encode('hex')
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']+="01"
# del(json_db['pool'])
# del(json_db['names'])
if vers > -1:
addrtype = oldaddrtype
return {'crypted':crypted}
def importprivkey(db, sec, label, reserve, keyishex, verbose=True, addrv=addrtype):
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 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, compressed)
public_key = GetPubKey(pkey, compressed)
addr = public_key_to_bc_address(public_key, addrv)
if verbose:
print "Address (%s): %s"%(aversions[addrv], addr)
print "Privkey (%s): %s"%(aversions[addrv], SecretToASecret(secret, compressed))
print "Hexprivkey: %s"%(secret.encode('hex'))
print "Hash160: %s"%(bc_address_to_hash_160(addr).encode('hex'))
if not compressed:
print "Pubkey: 04%.64x%.64x"%(pkey.pubkey.point.x(), pkey.pubkey.point.y())
else:
print "Pubkey: 0%d%.64x"%(2+(pkey.pubkey.point.y()&1), pkey.pubkey.point.x())
if int(secret.encode('hex'), 16)>_r:
print 'Beware, 0x%s is equivalent to 0x%.33x</b>'%(secret.encode('hex'), int(secret.encode('hex'), 16)-_r)
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 = 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 })
if not reserve:
update_wallet(db, 'name', { 'hash' : addr, 'name' : label })
return True
def balance(site, address):
page=urllib.urlopen("%s%s" % (site, address))
return page.read()
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 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 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, 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, compressed) )
print "Hexprivkey: %s" % secret.encode('hex')
print "Hash160: %s"%(bc_address_to_hash_160(addr).encode('hex'))
return True
def css_wui():
return """html, body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
body {
margin: 0px;
padding: 0px;
background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABLAAD/4QMpaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjAtYzA2MSA2NC4xNDA5NDksIDIwMTAvMTIvMDctMTA6NTc6MDEgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzUgV2luZG93cyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozRjJGNUI0ODZBN0YxMUUyQkZGRUNDQzgwNjYxQkJENCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozRjJGNUI0OTZBN0YxMUUyQkZGRUNDQzgwNjYxQkJENCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjNGMkY1QjQ2NkE3RjExRTJCRkZFQ0NDODA2NjFCQkQ0IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjNGMkY1QjQ3NkE3RjExRTJCRkZFQ0NDODA2NjFCQkQ0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+/+4ADkFkb2JlAGTAAAAAAf/bAIQAAwICAgICAwICAwUDAwMFBQQDAwQF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%3D%3D) repeat;
font-family: 'Open Sans', sans-serif;
font-size: 10pt;
color: #B0B0B0;
}
h1, h2, h3 {
margin: 0;
padding: 0;
}
h2
{
font-weight: 400;
font-family: 'Archivo Narrow', sans-serif;
font-size: 2.50em;
}
p, ol, ul {
margin-top: 0px;
}
p {
line-height: 180%;
}
strong {
}
a {
color: #1492C4;
}
a:hover {
text-decoration: none;
}
a img {
border: none;
}
img.border {
border: 10px solid rgba(255,255,255,.10);
}
img.alignleft {
float: left;
margin-right: 30px;
}
img.alignright {
float: right;
}
img.aligncenter {
margin: 0px auto;
}
hr {
display: none;
}
#retour-pyw{
overflow: auto;
}
#uptodate{
position:absolute;
top:0px;
right:0px;
background: rgba(0,0,0,0.70);
padding:10px;
}
#full-screen-background-image {
z-index: -999;
min-height: 100%;
min-width: 1024px;
width: 100%;
height: auto;
position: fixed;
top: 0;
left: 0;
}
#wrapper {
position: relative;
width: 100%;
min-height: 400px;
#margin: 30px auto;
margin-top:10px; #decalage p/r haut
}
#wrapper {
overflow: hidden;
}
.container {
width: 1000px;
margin: 0px auto;
}
.clearfix {
clear: both;
}
/** HEADER */
#header-wrapper-title
{
overflow: hidden;
height: 80px;
margin-bottom: 10px;
background: rgba(0,0,0,0);
}
#header-wrapper
{
overflow: hidden;
height: 50px;
margin-bottom: 20px;
background: rgba(0,0,0,0.70);
}
#header {
overflow: hidden;
}
/** LOGO */
#logo {
float: left;
#width: 300px;
height: 50px;
}
#logo h1, #logo p {
margin: 0px;
line-height: normal;
}
#logo h1 a {
padding-left: 00px;
text-decoration: none;
font-size: 2.50em;
font-weight: 400;
font-family: 'Archivo Narrow', sans-serif;
color: #FFFFFF;
}
/** MENU */
#menu {
float: left;
height: 50px;
}
#menu ul {
margin: 0px;
padding: 0px;
list-style: none;
line-height: normal;
}
#menu li {
float: left;
margin-right: 10px;
padding: 0px 5px 0px 5px;
}
#menu a {
display: block;
height: 50px;
padding: 0px 10px;
line-height: 50px;
text-decoration: none;
text-transform: uppercase;
color: #FFFFFF;
}
#menu a:hover {
text-decoration: none;
background: rgba(0,0,0,0.70);
}
#menu .active
{
background: rgba(0,0,0,0.70);
}
/** PAGE */
#page {
overflow: hidden;
margin-bottom: 20px;
}
/** CONTENT */
#content {
float: left;
width: 950px;
padding: 40px;
background: rgba(0,0,0,0.70);
}
#content h2 a
{
display: block;
padding: 0px 0px 20px 0px;
text-decoration: none;
color: #FFFFFF;
}
#content #box1
{
margin-bottom: 0px;
}
/** SIDEBAR */
#sidebar {
float: right;
width: 350px;
padding: 20px;
background: rgba(0,0,0,0.70);
}
#sidebar h2
{
padding: 0px 0px 00px 0px;
color: #FFFFFF;
}
/* Footer */
#footer {
overflow: hidden;
margin: 00px auto 0px auto;
padding: 10px 0px;
background: rgba(0,0,0,0.70);
}
#footer p {
text-align: center;
font-size: 12px;
}
#footer a {
}
/** LIST STYLE 1 */
ul.style1 {
margin: 0px;
padding: 10px 0px 0px 0px;
list-style: none;
}
ul.style1 li {
clear: both;
margin-bottom: 25px;
padding: 30px 0px 40px 0px;
border-top: 1px solid #000000;
box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
}
ul.style1 h3 {
padding-bottom: 5px;
font-size: 14px;
color: #FFFFFF;
}
ul.style1 p {
line-height: 150%;
}
ul.style1 .button-style {
float: left;
margin-top: 0px;
}
ul.style1 .first {
padding-top: 0px;
border-top: none;
box-shadow: none;
}
/** LIST STYLE 3 */
ul.style3 {
margin: 0px;
padding: 0px;
list-style: none;
}
ul.style3 li {
padding: 10px 0px 10px 0px;
border-top: 1px solid #000000;
box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
}
ul.style3 a {
text-decoration: none;
color: #949494;
}
ul.style3 a:hover {
text-decoration: underline;
}
ul.style3 .first {
padding-top: 0px;
border-top: none;
box-shadow: none;
}
ul.style3 .date {
width: 87px;
background-color: #1F768D;
margin-top: 20px;
height: 24px;
line-height: 24px;
text-align: center;
font-size: 12px;
color: #FFFFFF;
}
ul.style3 .first .date
{
margin-top: 0px;
}
.button-style
{
display: inline-block;
background-color: #1F768D;
margin-top: 0px;
padding: 5px 30px;
height: 24px;
line-height: 24px;
text-decoration: none;
text-align: center;
color: #FFFFFF;
}
.button-style-red
{
color: #ffffff;
display: inline-block;
background-color: #a12323;
margin-top: 20px;
padding: 5px 30px;
height: 24px;
line-height: 24px;
text-decoration: none;
text-align: center;
}
.entry
{
margin-bottom: 30px;
}
"""
def onclick_on_tab(page):
list=['DumpPage','ImportPage','DeletePage','InfoPage','AboutPage','PassphrasePage','TxPage']
r=''
for p in list:
if p!=page:
r+="document.getElementById('"+p+"').style.display='none';"
r+="document.getElementById('"+p+"Button').className='';"
r+="document.getElementById('"+page+"').style.display='block';"
r+="document.getElementById('"+page+"Button').className='active';"
return r
def html_wui(listcontent,uptodate_text):
global pywversion
return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Pywallet Web Interface - Pywallet """+pywversion+"""</title>
<!--link href='http://fonts.googleapis.com/css?family=Archivo+Narrow:400,700|Open+Sans:400,600,700' rel='stylesheet' type='text/css'-->
<!--link href="default.css" rel="stylesheet" type="text/css" media="all" /-->
<style type="text/css">
@font-face {
font-family: 'Archivo Narrow';
font-style: normal;
font-weight: 400;
src: local('Archivo Narrow Regular'), local('ArchivoNarrow-Regular'), url('data:application/x-font-woff;base64,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