Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Riccardo Spagni 2015-02-17 15:27:35 +02:00
commit c41fe53fd2
16 changed files with 270 additions and 88 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ locale/
.devlocaltmp/
*_trial_temp
packages
env/

View File

@ -120,7 +120,7 @@ def arg_parser():
parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
parser.add_option("-L", "--lang", dest="language", default=None, help="default language used in GUI")
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")

View File

@ -17,8 +17,8 @@ else
python setup.py install
fi
export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH
export PYTHONPATH="/usr/local/lib/python2.7/site-packages:$PYTHONPATH"
./electrum
./electrum "$@"
deactivate

View File

@ -13,5 +13,5 @@ Categories=Network;
StartupNotify=false
Terminal=false
Type=Application
MimeType=x-scheme-handler/bitcoin
MimeType=x-scheme-handler/bitcoin;

View File

@ -296,6 +296,8 @@ class ElectrumWindow(QMainWindow):
wallet.start_threads(self.network)
# load new wallet in gui
self.load_wallet(wallet)
# save path
self.config.set_key('default_wallet_path', filename)
@ -577,7 +579,7 @@ class ElectrumWindow(QMainWindow):
elif be == 'Insight.is':
block_explorer = 'http://live.insight.is/tx/'
elif be == "Blocktrail.com":
block_explorer = 'https://www.blocktrail.com/tx/'
block_explorer = 'https://www.blocktrail.com/BTC/tx/'
if not item: return
tx_hash = str(item.data(0, Qt.UserRole).toString())
@ -1907,39 +1909,44 @@ class ElectrumWindow(QMainWindow):
dialog.setModal(1)
dialog.setWindowTitle(_("Master Public Keys"))
main_layout = QGridLayout()
mpk_dict = self.wallet.get_master_public_keys()
# filter out the empty keys (PendingAccount)
mpk_dict = {acc:mpk for acc,mpk in mpk_dict.items() if mpk}
vbox = QVBoxLayout()
# only show the combobox in case multiple accounts are available
if len(mpk_dict) > 1:
combobox = QComboBox()
for name in mpk_dict:
combobox.addItem(name)
combobox.setCurrentIndex(0)
main_layout.addWidget(combobox, 1, 0)
gb = QGroupBox(_("Master Public Keys"))
vbox.addWidget(gb)
group = QButtonGroup()
first_button = None
for name in sorted(mpk_dict.keys()):
b = QRadioButton(gb)
b.setText(name)
group.addButton(b)
vbox.addWidget(b)
if not first_button:
first_button = b
account = unicode(combobox.currentText())
mpk_text = ShowQRTextEdit(text=mpk_dict[account])
mpk_text = ShowQRTextEdit()
mpk_text.setMaximumHeight(170)
mpk_text.selectAll() # for easy copying
main_layout.addWidget(mpk_text, 2, 0)
vbox.addWidget(mpk_text)
def show_mpk(account):
mpk = mpk_dict.get(unicode(account), "")
def show_mpk(b):
name = str(b.text())
mpk = mpk_dict.get(name, "")
mpk_text.setText(mpk)
mpk_text.selectAll() # for easy copying
combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc))
group.buttonReleased.connect(show_mpk)
first_button.setChecked(True)
show_mpk(first_button)
#combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc))
elif len(mpk_dict) == 1:
mpk = mpk_dict.values()[0]
mpk_text = ShowQRTextEdit(text=mpk)
mpk_text.setMaximumHeight(170)
mpk_text.selectAll() # for easy copying
main_layout.addWidget(mpk_text, 2, 0)
vbox.addWidget(mpk_text)
vbox = QVBoxLayout()
vbox.addLayout(main_layout)
vbox.addLayout(close_button(dialog))
dialog.setLayout(vbox)

View File

@ -201,9 +201,12 @@ class NetworkDialog(QDialog):
def change_server(self, host, protocol):
pp = self.servers.get(host, DEFAULT_PORTS)
if protocol and protocol not in protocol_letters:
protocol = None
if protocol:
port = pp.get(protocol)
if not port: protocol = None
if port is None:
protocol = None
if not protocol:
if 's' in pp.keys():
@ -217,15 +220,6 @@ class NetworkDialog(QDialog):
self.server_port.setText( port )
self.server_protocol.setCurrentIndex(protocol_letters.index(protocol))
if not self.servers: return
for p in protocol_letters:
i = protocol_letters.index(p)
j = self.server_protocol.model().index(i,0)
#if p not in pp.keys(): # and self.interface.is_connected:
# self.server_protocol.model().setData(j, QVariant(0), Qt.UserRole-1)
#else:
# self.server_protocol.model().setData(j, QVariant(33), Qt.UserRole-1)
def do_exec(self):

View File

@ -368,7 +368,7 @@ class Network(threading.Thread):
out['result'] = f(*params)
except BaseException as e:
out['error'] = str(e)
traceback.print_exc(file=sys.stout)
traceback.print_exc(file=sys.stdout)
print_error("network error", str(e))
self.response_queue.put(out)

View File

@ -43,42 +43,8 @@ import x509
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
ca_list = {}
ca_path = requests.certs.where()
def load_certificates():
try:
ca_f = open(ca_path, 'r')
except Exception:
print "ERROR: Could not open %s"%ca_path
print "ca-bundle.crt file should be placed in ~/.electrum/ca/ca-bundle.crt"
print "Documentation on how to download or create the file here: http://curl.haxx.se/docs/caextract.html"
print "Payment will continue with manual verification."
return False
c = ""
for line in ca_f:
if line == "-----BEGIN CERTIFICATE-----\n":
c = line
else:
c += line
if line == "-----END CERTIFICATE-----\n":
x = x509.X509()
try:
x.parse(c)
except Exception as e:
util.print_error("cannot parse cert:", e)
continue
ca_list[x.getFingerprint()] = x
ca_f.close()
util.print_error("%d certificates"%len(ca_list))
return True
load_certificates()
ca_list = x509.load_certificates(ca_path)
class PaymentRequest:
@ -190,8 +156,13 @@ class PaymentRequest:
verify = pubkey.hashAndVerify(sig, data)
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA256:
hashBytes = bytearray(hashlib.sha256(data).digest())
prefixBytes = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20])
verify = pubkey.verify(sig, prefixBytes + hashBytes)
verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + hashBytes)
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA384:
hashBytes = bytearray(hashlib.sha384(data).digest())
verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA384 + hashBytes)
elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA512:
hashBytes = bytearray(hashlib.sha512(data).digest())
verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes)
else:
self.error = "Algorithm not supported"
util.print_error(self.error, algo.getComponentByName('algorithm'))
@ -226,8 +197,7 @@ class PaymentRequest:
if paymntreq.pki_type == "x509+sha256":
hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
prefixBytes = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20])
verify = pubkey0.verify(sigBytes, prefixBytes + hashBytes)
verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)
elif paymntreq.pki_type == "x509+sha1":
verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
else:
@ -321,7 +291,6 @@ class PaymentRequest:
if __name__ == "__main__":
util.set_verbosity(True)
load_certificates()
try:
uri = sys.argv[1]

View File

@ -40,7 +40,7 @@ def hook(func):
def run_hook(name, *args):
SPECIAL_HOOKS = ['get_wizard_action']
SPECIAL_HOOKS = ['get_wizard_action','installwizard_restore']
results = []
f_list = hooks.get(name,[])
for p, f in f_list:
@ -71,6 +71,7 @@ class BasePlugin:
def __init__(self, config, name):
self.name = name
self.config = config
self.wallet = None
# add self to hooks
for k in dir(self):
if k in hook_names:

View File

@ -67,7 +67,7 @@ class WalletStorage(object):
# path in config file
path = config.get('default_wallet_path')
if path:
if path and os.path.exists(path):
return path
# default path
@ -1494,7 +1494,7 @@ class Wallet_2of2(BIP32_Wallet, Mnemonic):
def get_master_public_keys(self):
xpub1 = self.master_public_keys.get("x1/")
xpub2 = self.master_public_keys.get("x2/")
return {'x1':xpub1, 'x2':xpub2}
return { 'Self':xpub1, 'Cosigner':xpub2 }
def get_action(self):
xpub1 = self.master_public_keys.get("x1/")

View File

@ -23,6 +23,7 @@ import sys
import pyasn1
import pyasn1_modules
import tlslite
import util
# workaround https://github.com/trevp/tlslite/issues/15
tlslite.utils.cryptomath.pycryptoLoaded = False
@ -41,8 +42,18 @@ from pyasn1_modules.rfc2459 import id_at_organizationalUnitName as OU_NAME
from pyasn1_modules.rfc2459 import id_ce_basicConstraints, BasicConstraints
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
# algo OIDs
ALGO_RSA_SHA1 = ObjectIdentifier('1.2.840.113549.1.1.5')
ALGO_RSA_SHA256 = ObjectIdentifier('1.2.840.113549.1.1.11')
ALGO_RSA_SHA384 = ObjectIdentifier('1.2.840.113549.1.1.12')
ALGO_RSA_SHA512 = ObjectIdentifier('1.2.840.113549.1.1.13')
# prefixes, see http://stackoverflow.com/questions/3713774/c-sharp-how-to-calculate-asn-1-der-encoding-of-a-particular-hash-algorithm
PREFIX_RSA_SHA256 = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20])
PREFIX_RSA_SHA384 = bytearray([0x30,0x41,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02,0x05,0x00,0x04,0x30])
PREFIX_RSA_SHA512 = bytearray([0x30,0x51,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03,0x05,0x00,0x04,0x40])
class CertificateError(Exception):
pass
@ -214,3 +225,21 @@ class X509(tlslite.X509):
class X509CertChain(tlslite.X509CertChain):
pass
def load_certificates(ca_path):
ca_list = {}
with open(ca_path, 'r') as f:
s = f.read()
bList = tlslite.utils.pem.dePemList(s, "CERTIFICATE")
for b in bList:
x = X509()
try:
x.parseBinary(b)
except Exception as e:
util.print_error("cannot parse cert:", e)
continue
ca_list[x.getFingerprint()] = x
return ca_list

View File

@ -15,8 +15,7 @@ import platform
try:
import amodem.audio
import amodem.recv
import amodem.send
import amodem.main
import amodem.config
print_error('Audio MODEM is available.')
amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))
@ -115,7 +114,7 @@ class Plugin(BasePlugin):
with self._audio_interface() as interface:
src = BytesIO(blob)
dst = interface.player()
amodem.send.main(config=self.modem_config, src=src, dst=dst)
amodem.main.send(config=self.modem_config, src=src, dst=dst)
except Exception:
traceback.print_exc()
@ -132,7 +131,7 @@ class Plugin(BasePlugin):
with self._audio_interface() as interface:
src = interface.recorder()
dst = BytesIO()
amodem.recv.main(config=self.modem_config, src=src, dst=dst)
amodem.main.recv(config=self.modem_config, src=src, dst=dst)
return dst.getvalue()
except Exception:
traceback.print_exc()

View File

@ -17,6 +17,7 @@ from electrum_gui.qt.amountedit import AmountEdit
EXCHANGES = ["BitcoinAverage",
"BitcoinVenezuela",
"BTCParalelo",
"Bitcurex",
"Bitmarket",
"BitPay",
@ -63,6 +64,26 @@ class Exchanger(threading.Thread):
except Exception:
raise
return json_resp
def get_json_insecure(self, site, get_string):
""" get_json_insecure shouldn't be used in production releases
It doesn't use SSL, and so prices could be manipulated by a middle man
This should be used ONLY when developing plugins when you don't have a
SSL certificate that validates against HTTPSConnection
"""
try:
connection = httplib.HTTPConnection(site)
connection.request("GET", get_string, headers={"User-Agent":"Electrum"})
except Exception:
raise
resp = connection.getresponse()
if resp.reason == httplib.responses[httplib.NOT_FOUND]:
raise
try:
json_resp = json.loads(resp.read())
except Exception:
raise
return json_resp
def exchange(self, btc_amount, quote_currency):
@ -82,6 +103,7 @@ class Exchanger(threading.Thread):
update_rates = {
"BitcoinAverage": self.update_ba,
"BitcoinVenezuela": self.update_bv,
"BTCParalelo": self.update_bpl,
"Bitcurex": self.update_bx,
"Bitmarket": self.update_bm,
"BitPay": self.update_bp,
@ -110,6 +132,9 @@ class Exchanger(threading.Thread):
def update_cd(self):
try:
resp_currencies = self.get_json('api.coindesk.com', "/v1/bpi/supported-currencies.json")
except SSLError:
print("SSL Error when accesing coindesk")
return
except Exception:
return
@ -138,6 +163,9 @@ class Exchanger(threading.Thread):
try:
resp_rate = self.get_json('api.itbit.com', "/v1/markets/XBT" + str(current_cur) + "/ticker")
quote_currencies[str(current_cur)] = decimal.Decimal(str(resp_rate["lastPrice"]))
except SSLError:
print("SSL Error when accesing itbit")
return
except Exception:
return
with self.lock:
@ -147,6 +175,9 @@ class Exchanger(threading.Thread):
def update_wd(self):
try:
winkresp = self.get_json('winkdex.com', "/api/v0/price")
except SSLError:
print("SSL Error when accesing winkdex")
return
except Exception:
return
quote_currencies = {"USD": 0.0}
@ -162,6 +193,9 @@ class Exchanger(threading.Thread):
def update_cv(self):
try:
jsonresp = self.get_json('www.cavirtex.com', "/api/CAD/ticker.json")
except SSLError:
print("SSL Error when accesing cavirtex")
return
except Exception:
return
quote_currencies = {"CAD": 0.0}
@ -177,6 +211,9 @@ class Exchanger(threading.Thread):
def update_bm(self):
try:
jsonresp = self.get_json('www.bitmarket.pl', "/json/BTCPLN/ticker.json")
except SSLError:
print("SSL Error when accesing bitmarket")
return
except Exception:
return
quote_currencies = {"PLN": 0.0}
@ -192,6 +229,9 @@ class Exchanger(threading.Thread):
def update_bx(self):
try:
jsonresp = self.get_json('pln.bitcurex.com', "/data/ticker.json")
except SSLError:
print("SSL Error when accesing bitcurex")
return
except Exception:
return
quote_currencies = {"PLN": 0.0}
@ -207,6 +247,9 @@ class Exchanger(threading.Thread):
def update_CNY(self):
try:
jsonresp = self.get_json('data.btcchina.com', "/data/ticker")
except SSLError:
print("SSL Error when accesing btcchina")
return
except Exception:
return
quote_currencies = {"CNY": 0.0}
@ -222,6 +265,9 @@ class Exchanger(threading.Thread):
def update_bp(self):
try:
jsonresp = self.get_json('bitpay.com', "/api/rates")
except SSLError:
print("SSL Error when accesing bitpay")
return
except Exception:
return
quote_currencies = {}
@ -237,6 +283,9 @@ class Exchanger(threading.Thread):
def update_cb(self):
try:
jsonresp = self.get_json('coinbase.com', "/api/v1/currencies/exchange_rates")
except SSLError:
print("SSL Error when accesing coinbase")
return
except Exception:
return
@ -255,6 +304,9 @@ class Exchanger(threading.Thread):
def update_bc(self):
try:
jsonresp = self.get_json('blockchain.info', "/ticker")
except SSLError:
print("SSL Error when accesing blockchain")
return
except Exception:
return
quote_currencies = {}
@ -270,6 +322,9 @@ class Exchanger(threading.Thread):
def update_lb(self):
try:
jsonresp = self.get_json('localbitcoins.com', "/bitcoinaverage/ticker-all-currencies/")
except SSLError:
print("SSL Error when accesing localbitcoins")
return
except Exception:
return
quote_currencies = {}
@ -285,23 +340,52 @@ class Exchanger(threading.Thread):
def update_bv(self):
try:
jsonresp = self.get_json('api.bitcoinvenezuela.com', "/")
jsonresp = self.get_json_insecure('api.bitcoinvenezuela.com', "/")
print("**WARNING**: update_bv is using an insecure connection, shouldn't be used on production")
except SSLError:
print("SSL Error when accesing bitcoinvenezuela")
return
except Exception:
return
quote_currencies = {}
try:
for r in jsonresp["BTC"]:
quote_currencies[r] = Decimal(jsonresp["BTC"][r])
with self.lock:
self.quote_currencies = quote_currencies
except KeyError:
pass
print ("KeyError")
self.parent.set_currencies(quote_currencies)
def update_bpl(self):
try:
jsonresp = self.get_json_insecure('btcparalelo.com', "/api/price")
print("**WARNING**: update_bpl is using an insecure connection, shouldn't be used on production")
except SSLError:
print("SSL Error when accesing btcparalelo")
return
except Exception:
return
quote_currencies = {}
try:
quote_currencies = {"VEF": Decimal(jsonresp["price"])}
with self.lock:
self.quote_currencies = quote_currencies
except KeyError:
print ("KeyError")
self.parent.set_currencies(quote_currencies)
def update_ba(self):
try:
jsonresp = self.get_json('api.bitcoinaverage.com', "/ticker/global/all")
except SSLError:
print("SSL Error when accesing bitcoinaverage")
return
except Exception:
return
quote_currencies = {}
@ -411,7 +495,6 @@ class Plugin(BasePlugin):
@hook
def load_wallet(self, wallet):
self.wallet = wallet
tx_list = {}
for item in self.wallet.get_tx_history(self.wallet.storage.get("current_account", None)):
tx_hash, conf, is_mine, value, fee, balance, timestamp = item
@ -471,6 +554,8 @@ class Plugin(BasePlugin):
return
if not self.resp_hist:
return
if not self.wallet:
return
self.win.is_edit = True
self.win.history_list.setColumnCount(6)

View File

@ -96,6 +96,7 @@ class Plugin(BasePlugin):
print_error("trezor: clear session")
if self.wallet and self.wallet.client:
self.wallet.client.clear_session()
self.wallet.client.transport.close()
@hook
def load_wallet(self, wallet):
@ -131,6 +132,10 @@ class Plugin(BasePlugin):
self.wallet.trezor_sign(tx)
except Exception as e:
tx.error = str(e)
@hook
def receive_menu(self, menu, addrs):
if not self.wallet.is_watching_only() and self.wallet.atleast_version(1, 3) and len(addrs) == 1:
menu.addAction(_("Show on TREZOR"), lambda: self.wallet.show_address(addrs[0]))
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
@ -212,15 +217,22 @@ class TrezorWallet(BIP32_HD_Wallet):
except:
give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.')
self.client = QtGuiTrezorClient(self.transport)
if (self.client.features.major_version == 1 and self.client.features.minor_version < 2) or (self.client.features.major_version == 1 and self.client.features.minor_version == 2 and self.client.features.patch_version < 1):
give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com')
self.client.set_tx_api(self)
#self.client.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure
self.client.bad = False
self.device_checked = False
self.proper_device = False
if not self.atleast_version(1, 2, 1):
give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com')
return self.client
def compare_version(self, major, minor=0, patch=0):
features = self.get_client().features
return cmp([features.major_version, features.minor_version, features.patch_version], [major, minor, patch])
def atleast_version(self, major, minor=0, patch=0):
return self.compare_version(major, minor, patch) >= 0
def address_id(self, address):
account_id, (change, address_index) = self.get_address_index(address)
return "44'/0'/%s'/%d/%d" % (account_id, change, address_index)
@ -277,6 +289,21 @@ class TrezorWallet(BIP32_HD_Wallet):
# twd.emit(SIGNAL('trezor_done'))
#return str(decrypted_msg)
def show_address(self, address):
if not self.check_proper_device():
give_error('Wrong device or password')
try:
address_path = self.address_id(address)
address_n = self.get_client().expand_path(address_path)
except Exception, e:
give_error(e)
try:
self.get_client().get_address('Bitcoin', address_n, True)
except Exception, e:
give_error(e)
finally:
twd.emit(SIGNAL('trezor_done'))
def sign_message(self, address, message, password):
if not self.check_proper_device():
give_error('Wrong device or password')
@ -426,6 +453,8 @@ class TrezorQtGuiMixin(object):
message = "Confirm transaction fee on Trezor device to continue"
elif msg.code == 7:
message = "Confirm message to sign on Trezor device to continue"
elif msg.code == 10:
message = "Confirm address on Trezor device to continue"
else:
message = "Check Trezor device to continue"
twd.start(message)

59
scripts/bip70 Executable file
View File

@ -0,0 +1,59 @@
# create a BIP70 payment request signed with a certificate
import tlslite
import time
import hashlib
from electrum import paymentrequest_pb2 as pb2
from electrum.transaction import Transaction
from electrum import bitcoin
from electrum import x509
chain_file = 'mychain.pem'
cert_file = 'mycert.pem'
amount = 1000000
address = "18U5kpCAU4s8weFF8Ps5n8HAfpdUjDVF64"
memo = "blah"
out_file = "payreq"
with open(chain_file, 'r') as f:
chain = tlslite.X509CertChain()
chain.parsePemList(f.read())
certificates = pb2.X509Certificates()
certificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List))
with open(cert_file, 'r') as f:
rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read())
def make_payment_request(amount, script, memo):
"""Generates a http PaymentRequest object"""
pd = pb2.PaymentDetails()
pd.outputs.add(amount=amount, script=script)
now = int(time.time())
pd.time = now
pd.expires = now + 15*60
pd.memo = memo
pd.payment_url = 'http://payment_ack.url'
pr = pb2.PaymentRequest()
pr.serialized_payment_details = pd.SerializeToString()
pr.pki_type = 'x509+sha256'
pr.pki_data = certificates.SerializeToString()
pr.signature = ''
msgBytes = bytearray(pr.SerializeToString())
hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
sig = rsakey.sign(x509.PREFIX_RSA_SHA256 + hashBytes)
pr.signature = bytes(sig)
return pr.SerializeToString()
script = Transaction.pay_script('address', address).decode('hex')
pr_string = make_payment_request(amount, script, memo)
with open(out_file,'wb') as f:
f.write(pr_string)
print "Payment request was written to file '%s'"%out_file

9
scripts/estimate_fee Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
import util, json
peers = util.get_peers()
results = util.send_request(peers, {'method':'blockchain.estimatefee','params':[1]})
print json.dumps(results, indent=4)