electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 5717b43661be2b0616528911b8919f1852139a57
parent 728ae0d1848ee975c466ca939f9cdd0d39153114
Author: thomasv <thomasv@gitorious>
Date:   Fri, 19 Oct 2012 14:55:01 +0200

separate bitcoin related functions from wallet.py

Diffstat:
Mlib/__init__.py | 1+
Alib/bitcoin.py | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/util.py | 27+++++++++++++++++++++++++--
Mlib/wallet.py | 219++-----------------------------------------------------------------------------
Mscripts/validate_tx | 12+-----------
Msetup.py | 1+
6 files changed, 242 insertions(+), 229 deletions(-)

diff --git a/lib/__init__.py b/lib/__init__.py @@ -1,3 +1,4 @@ from wallet import Wallet, format_satoshis from interface import WalletSynchronizer, Interface, pick_random_server, DEFAULT_SERVERS from simple_config import SimpleConfig +import bitcoin diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import hashlib, base64, ecdsa, re + + +def rev_hex(s): + return s.decode('hex')[::-1].encode('hex') + +def int_to_hex(i, length=1): + s = hex(i)[2:].rstrip('L') + s = "0"*(2*length - len(s)) + s + return rev_hex(s) + + +def Hash(data): + return hashlib.sha256(hashlib.sha256(data).digest()).digest() + + +############ functions from pywallet ##################### + +addrtype = 0 + +def hash_160(public_key): + try: + md = hashlib.new('ripemd160') + md.update(hashlib.sha256(public_key).digest()) + return md.digest() + except: + import ripemd + md = ripemd.new(hashlib.sha256(public_key).digest()) + return md.digest() + + +def public_key_to_bc_address(public_key): + h160 = hash_160(public_key) + return hash_160_to_bc_address(h160) + +def hash_160_to_bc_address(h160): + vh160 = chr(addrtype) + 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] + +def encode_point(pubkey, compressed=False): + order = generator_secp256k1.order() + p = pubkey.pubkey.point + x_str = ecdsa.util.number_to_string(p.x(), order) + y_str = ecdsa.util.number_to_string(p.y(), order) + if compressed: + return chr(2 + (p.y() & 1)) + x_str + else: + return chr(4) + pubkey.to_string() #x_str + y_str + +__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 + + +def EncodeBase58Check(vchIn): + hash = Hash(vchIn) + return b58encode(vchIn + hash[0:4]) + +def DecodeBase58Check(psz): + vchRet = b58decode(psz, None) + key = vchRet[0:-4] + csum = vchRet[-4:] + hash = Hash(key) + cs32 = hash[0:4] + if cs32 != csum: + return None + else: + return key + +def PrivKeyToSecret(privkey): + return privkey[9:9+32] + +def SecretToASecret(secret): + vchIn = chr(addrtype+128) + secret + return EncodeBase58Check(vchIn) + +def ASecretToSecret(key): + vch = DecodeBase58Check(key) + if vch and vch[0] == chr(addrtype+128): + return vch[1:] + else: + return False + +########### end pywallet functions ####################### + +# secp256k1, http://www.oid-info.com/get/1.3.132.0.10 +_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL +_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L +_b = 0x0000000000000000000000000000000000000000000000000000000000000007L +_a = 0x0000000000000000000000000000000000000000000000000000000000000000L +_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L +_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L +curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) +generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) +oid_secp256k1 = (1,3,132,0,10) +SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) + + +def filter(s): + out = re.sub('( [^\n]*|)\n','',s) + out = out.replace(' ','') + out = out.replace('\n','') + return out + +def raw_tx( inputs, outputs, for_sig = None ): + s = int_to_hex(1,4) + ' version\n' + s += int_to_hex( len(inputs) ) + ' number of inputs\n' + for i in range(len(inputs)): + _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i] + s += p_hash.decode('hex')[::-1].encode('hex') + ' prev hash\n' + s += int_to_hex(p_index,4) + ' prev index\n' + if for_sig is None: + sig = sig + chr(1) # hashtype + script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig) + script += sig.encode('hex') + ' sig\n' + pubkey = chr(4) + pubkey + script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey) + script += pubkey.encode('hex') + ' pubkey\n' + elif for_sig==i: + script = p_script + ' scriptsig \n' + else: + script='' + s += int_to_hex( len(filter(script))/2 ) + ' script length \n' + s += script + s += "ffffffff" + ' sequence\n' + s += int_to_hex( len(outputs) ) + ' number of outputs\n' + for output in outputs: + addr, amount = output + s += int_to_hex( amount, 8) + ' amount: %d\n'%amount + script = '76a9' # op_dup, op_hash_160 + script += '14' # push 0x14 bytes + script += bc_address_to_hash_160(addr).encode('hex') + script += '88ac' # op_equalverify, op_checksig + s += int_to_hex( len(filter(script))/2 ) + ' script length \n' + s += script + ' script \n' + s += int_to_hex(0,4) # lock time + if for_sig is not None: s += int_to_hex(1, 4) # hash type + return s + + diff --git a/lib/util.py b/lib/util.py @@ -1,6 +1,5 @@ -import os +import os, sys import platform -import sys def print_error(*args): # Stringify args @@ -8,6 +7,7 @@ def print_error(*args): sys.stderr.write(" ".join(args) + "\n") sys.stderr.flush() + def user_dir(): if "HOME" in os.environ: return os.path.join(os.environ["HOME"], ".electrum") @@ -18,6 +18,7 @@ def user_dir(): else: raise BaseException("No home directory found in environment variables.") + def appdata_dir(): """Find the path to the application data directory; add an electrum folder and return path.""" if platform.system() == "Windows": @@ -30,9 +31,11 @@ def appdata_dir(): else: raise Exception("Unknown system") + def get_resource_path(*args): return os.path.join(".", *args) + def local_data_dir(): """Return path to the data folder.""" assert sys.argv @@ -40,3 +43,23 @@ def local_data_dir(): local_data = os.path.join(prefix_path, "data") return local_data + +def format_satoshis(x, is_diff=False, num_zeros = 0): + from decimal import Decimal + s = Decimal(x) + sign, digits, exp = s.as_tuple() + digits = map(str, digits) + while len(digits) < 9: + digits.insert(0,'0') + digits.insert(-8,'.') + s = ''.join(digits).rstrip('0') + if sign: + s = '-' + s + elif is_diff: + s = "+" + s + + p = s.find('.') + s += "0"*( 1 + num_zeros - ( len(s) - p )) + s += " "*( 9 - ( len(s) - p )) + s = " "*( 5 - ( p )) + s + return s diff --git a/lib/wallet.py b/lib/wallet.py @@ -30,233 +30,21 @@ import aes import ecdsa from ecdsa.util import string_to_number, number_to_string -from util import print_error -from util import user_dir - -############ functions from pywallet ##################### - -addrtype = 0 - -def hash_160(public_key): - try: - md = hashlib.new('ripemd160') - md.update(hashlib.sha256(public_key).digest()) - return md.digest() - except: - import ripemd - md = ripemd.new(hashlib.sha256(public_key).digest()) - return md.digest() - - -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) - -def hash_160_to_bc_address(h160): - vh160 = chr(addrtype) + 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] - -def encode_point(pubkey, compressed=False): - order = generator_secp256k1.order() - p = pubkey.pubkey.point - x_str = ecdsa.util.number_to_string(p.x(), order) - y_str = ecdsa.util.number_to_string(p.y(), order) - if compressed: - return chr(2 + (p.y() & 1)) + x_str - else: - return chr(4) + pubkey.to_string() #x_str + y_str - -__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 - - -def Hash(data): - return hashlib.sha256(hashlib.sha256(data).digest()).digest() - -def EncodeBase58Check(vchIn): - hash = Hash(vchIn) - return b58encode(vchIn + hash[0:4]) - -def DecodeBase58Check(psz): - vchRet = b58decode(psz, None) - key = vchRet[0:-4] - csum = vchRet[-4:] - hash = Hash(key) - cs32 = hash[0:4] - if cs32 != csum: - return None - else: - return key - -def PrivKeyToSecret(privkey): - return privkey[9:9+32] - -def SecretToASecret(secret): - vchIn = chr(addrtype+128) + secret - return EncodeBase58Check(vchIn) - -def ASecretToSecret(key): - vch = DecodeBase58Check(key) - if vch and vch[0] == chr(addrtype+128): - return vch[1:] - else: - return False - -########### end pywallet functions ####################### - +from util import print_error, user_dir, format_satoshis +from bitcoin import * # URL decode _ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE) urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x) - -def int_to_hex(i, length=1): - s = hex(i)[2:].rstrip('L') - s = "0"*(2*length - len(s)) + s - return s.decode('hex')[::-1].encode('hex') - - -# AES +# AES encryption EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) - -# secp256k1, http://www.oid-info.com/get/1.3.132.0.10 -_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL -_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L -_b = 0x0000000000000000000000000000000000000000000000000000000000000007L -_a = 0x0000000000000000000000000000000000000000000000000000000000000000L -_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L -_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L -curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) -generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) -oid_secp256k1 = (1,3,132,0,10) -SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) - - -def filter(s): - out = re.sub('( [^\n]*|)\n','',s) - out = out.replace(' ','') - out = out.replace('\n','') - return out - -def raw_tx( inputs, outputs, for_sig = None ): - s = int_to_hex(1,4) + ' version\n' - s += int_to_hex( len(inputs) ) + ' number of inputs\n' - for i in range(len(inputs)): - _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i] - s += p_hash.decode('hex')[::-1].encode('hex') + ' prev hash\n' - s += int_to_hex(p_index,4) + ' prev index\n' - if for_sig is None: - sig = sig + chr(1) # hashtype - script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig) - script += sig.encode('hex') + ' sig\n' - pubkey = chr(4) + pubkey - script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey) - script += pubkey.encode('hex') + ' pubkey\n' - elif for_sig==i: - script = p_script + ' scriptsig \n' - else: - script='' - s += int_to_hex( len(filter(script))/2 ) + ' script length \n' - s += script - s += "ffffffff" + ' sequence\n' - s += int_to_hex( len(outputs) ) + ' number of outputs\n' - for output in outputs: - addr, amount = output - s += int_to_hex( amount, 8) + ' amount: %d\n'%amount - script = '76a9' # op_dup, op_hash_160 - script += '14' # push 0x14 bytes - script += bc_address_to_hash_160(addr).encode('hex') - script += '88ac' # op_equalverify, op_checksig - s += int_to_hex( len(filter(script))/2 ) + ' script length \n' - s += script + ' script \n' - s += int_to_hex(0,4) # lock time - if for_sig is not None: s += int_to_hex(1, 4) # hash type - return s - - - - -def format_satoshis(x, is_diff=False, num_zeros = 0): - from decimal import Decimal - s = Decimal(x) - sign, digits, exp = s.as_tuple() - digits = map(str, digits) - while len(digits) < 9: - digits.insert(0,'0') - digits.insert(-8,'.') - s = ''.join(digits).rstrip('0') - if sign: - s = '-' + s - elif is_diff: - s = "+" + s - - p = s.find('.') - s += "0"*( 1 + num_zeros - ( len(s) - p )) - s += " "*( 9 - ( len(s) - p )) - s = " "*( 5 - ( p )) + s - return s - - from version import ELECTRUM_VERSION, SEED_VERSION - class Wallet: def __init__(self, config={}): @@ -285,7 +73,6 @@ class Wallet: self.addressbook = config.get('contacts', []) # outgoing addresses, for payments self.imported_keys = config.get('imported_keys',{}) - # not saved self.receipt = None # next receipt self.tx_history = {} diff --git a/scripts/validate_tx b/scripts/validate_tx @@ -2,21 +2,11 @@ import sys, hashlib from electrum import Interface +from electrum.bitcoin import Hash, rev_hex, int_to_hex """validate a transaction (SPV)""" -Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest() - -def rev_hex(s): - return s.decode('hex')[::-1].encode('hex') - -def int_to_hex(i, length=1): - s = hex(i)[2:].rstrip('L') - s = "0"*(2*length - len(s)) + s - return rev_hex(s) - - i = Interface({'server':'ecdsa.org:50002:s'}) i.start() diff --git a/setup.py b/setup.py @@ -59,6 +59,7 @@ setup(name = "Electrum", 'electrum.bmp', 'electrum.msqr', 'electrum.util', + 'electrum.bitcoin', 'electrum.i18n'], description = "Lightweight Bitcoin Wallet", author = "thomasv",