Neil Booth
8 years ago
57 changed files with 3374 additions and 725 deletions
@ -0,0 +1,76 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# Copyright (c) 2017, Neil Booth |
|||
# |
|||
# All rights reserved. |
|||
# |
|||
# See the file "LICENCE" for information about the copyright |
|||
# and warranty status of this software. |
|||
|
|||
'''Script to compact the history database. This should save space and |
|||
will reset the flush counter to a low number, avoiding overflow when |
|||
the flush count reaches 65,536. |
|||
|
|||
This needs to lock the database so ElectrumX must not be running - |
|||
shut it down cleanly first. |
|||
|
|||
It is recommended you run this script with the same environment as |
|||
ElectrumX. However it is intended to be runnable with just |
|||
DB_DIRECTORY and COIN set (COIN defaults as for ElectrumX). |
|||
|
|||
If you use daemon tools, you might run this script like so: |
|||
|
|||
envdir /path/to/the/environment/directory ./compact_history.py |
|||
|
|||
Depending on your hardware this script may take up to 6 hours to |
|||
complete; it logs progress regularly. |
|||
|
|||
Compaction can be interrupted and restarted harmlessly and will pick |
|||
up where it left off. However, if you restart ElectrumX without |
|||
running the compaction to completion, it will not benefit and |
|||
subsequent compactions will restart from the beginning. |
|||
''' |
|||
|
|||
import logging |
|||
import sys |
|||
import traceback |
|||
from os import environ |
|||
|
|||
from server.env import Env |
|||
from server.db import DB |
|||
|
|||
|
|||
def compact_history(): |
|||
if sys.version_info < (3, 5, 3): |
|||
raise RuntimeError('Python >= 3.5.3 is required to run ElectrumX') |
|||
|
|||
environ['DAEMON_URL'] = '' # Avoid Env erroring out |
|||
env = Env() |
|||
db = DB(env) |
|||
|
|||
assert not db.first_sync |
|||
# Continue where we left off, if interrupted |
|||
if db.comp_cursor == -1: |
|||
db.comp_cursor = 0 |
|||
|
|||
db.comp_flush_count = max(db.comp_flush_count, 1) |
|||
limit = 8 * 1000 * 1000 |
|||
|
|||
while db.comp_cursor != -1: |
|||
db._compact_history(limit) |
|||
|
|||
|
|||
def main(): |
|||
logging.basicConfig(level=logging.INFO) |
|||
logging.info('Starting history compaction...') |
|||
try: |
|||
compact_history() |
|||
except Exception: |
|||
traceback.print_exc() |
|||
logging.critical('History compaction terminated abnormally') |
|||
else: |
|||
logging.info('History compaction complete') |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
main() |
@ -0,0 +1,3 @@ |
|||
#!/bin/sh |
|||
echo "Launching ElectrumX server..." |
|||
exec 2>&1 envdir ./env /bin/sh -c 'setuidgid $USERNAME python3 $ELECTRUMX' |
@ -0,0 +1,13 @@ |
|||
#!/bin/sh |
|||
########################### |
|||
#Installation of Python 3.6 |
|||
########################### |
|||
|
|||
sudo add-apt-repository ppa:jonathonf/python-3.6 |
|||
sudo apt-get update && sudo apt-get install python3.6 python3.6-dev |
|||
|
|||
cd /home/username |
|||
git clone https://github.com/kyuupichan/electrumx.git |
|||
cd electrumx |
|||
sudo python3.6 setup.py install |
|||
|
@ -0,0 +1,26 @@ |
|||
#!/bin/sh |
|||
################### |
|||
# install electrumx |
|||
################### |
|||
|
|||
# upgrade raspbian to 'stretch' distribution for python 3.5 support |
|||
sudo echo 'deb http://mirrordirector.raspbian.org/raspbian/ testing main contrib non-free rpi' > /etc/apt/sources.list.d/stretch.list |
|||
sudo apt-get update |
|||
sudo apt-get dist-upgrade |
|||
sudo apt-get autoremove |
|||
|
|||
# install electrumx dependencies |
|||
sudo apt-get install python3-pip |
|||
sudo apt-get install build-essential libc6-dev |
|||
sudo apt-get install libncurses5-dev libncursesw5-dev |
|||
sudo apt install libreadline6-dev/stable libreadline6/stable |
|||
sudo apt-get install libleveldb-dev |
|||
sudo apt-get install git |
|||
sudo pip3 install plyvel |
|||
sudo pip3 install irc |
|||
|
|||
# install electrumx |
|||
git clone https://github.com/kyuupichan/electrumx.git |
|||
cd electrumx |
|||
sudo python3 setup.py install |
|||
|
@ -0,0 +1,37 @@ |
|||
#!/bin/sh |
|||
############### |
|||
# run_electrumx |
|||
############### |
|||
|
|||
# configure electrumx |
|||
export COIN=Bitcoin |
|||
export DAEMON_URL=http://rpcuser:rpcpassword@127.0.0.1 |
|||
export NET=mainnet |
|||
export CACHE_MB=400 |
|||
export DB_DIRECTORY=/home/username/.electrumx/db |
|||
export SSL_CERTFILE=/home/username/.electrumx/certfile.crt |
|||
export SSL_KEYFILE=/home/username/.electrumx/keyfile.key |
|||
export BANNER_FILE=/home/username/.electrumx/banner |
|||
export DONATION_ADDRESS=your-donation-address |
|||
|
|||
# connectivity |
|||
export HOST= |
|||
export TCP_PORT=50001 |
|||
export SSL_PORT=50002 |
|||
|
|||
# visibility |
|||
export IRC= |
|||
export IRC_NICK=hostname |
|||
export REPORT_HOST=hostname.com |
|||
export RPC_PORT=8000 |
|||
|
|||
# run electrumx |
|||
ulimit -n 10000 |
|||
/usr/local/bin/electrumx_server.py 2>> /home/username/.electrumx/electrumx.log >> /home/username/.electrumx/electrumx.log & |
|||
|
|||
###################### |
|||
# auto-start electrumx |
|||
###################### |
|||
|
|||
# add this line to crontab -e |
|||
# @reboot /path/to/run_electrumx.sh |
@ -1,2 +1,3 @@ |
|||
Neil Booth: creator and maintainer |
|||
Johann Bauer: backend DB abstraction |
|||
Johann Bauer: backend DB abstraction |
|||
John Jegutanis: alt-chain integrations |
@ -1,3 +0,0 @@ |
|||
j#!/bin/sh |
|||
echo "Launching ElectrumX server..." |
|||
exec 2>&1 envdir ./env /bin/sh -c 'envuidgid $USERNAME python3 $ELECTRUMX' |
@ -1,5 +1,5 @@ |
|||
# Server name and protocol versions |
|||
|
|||
VERSION = 'ElectrumX 1.0' |
|||
VERSION = 'ElectrumX 1.0.13' |
|||
PROTOCOL_MIN = '1.0' |
|||
PROTOCOL_MAX = '1.0' |
|||
|
@ -0,0 +1,17 @@ |
|||
{ |
|||
"hash": "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", |
|||
"size": 957, |
|||
"height": 100000, |
|||
"merkleroot": "f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766", |
|||
"tx": [ |
|||
"8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87", |
|||
"fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4", |
|||
"6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4", |
|||
"e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d" |
|||
], |
|||
"time": 1293623863, |
|||
"nonce": 274148111, |
|||
"bits": "1b04864c", |
|||
"previousblockhash": "000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250", |
|||
"block": "0100000050120119172a610421a6c3011dd330d9df07b63616c2cc1f1cd00200000000006657a9252aacd5c0b2940996ecff952228c3067cc38d4885efb5a4ac4247e9f337221b4d4c86041b0f2b57100401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a010000004341041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84ac000000000100000001032e38e9c0a84c6046d687d10556dcacc41d275ec55fc00779ac88fdf357a187000000008c493046022100c352d3dd993a981beba4a63ad15c209275ca9470abfcd57da93b58e4eb5dce82022100840792bc1f456062819f15d33ee7055cf7b5ee1af1ebcc6028d9cdb1c3af7748014104f46db5e9d61a9dc27b8d64ad23e7383a4e6ca164593c2527c038c0857eb67ee8e825dca65046b82c9331586c82e0fd1f633f25f87c161bc6f8a630121df2b3d3ffffffff0200e32321000000001976a914c398efa9c392ba6013c5e04ee729755ef7f58b3288ac000fe208010000001976a914948c765a6914d43f2a7ac177da2c2f6b52de3d7c88ac000000000100000001c33ebff2a709f13d9f9a7569ab16a32786af7d7e2de09265e41c61d078294ecf010000008a4730440220032d30df5ee6f57fa46cddb5eb8d0d9fe8de6b342d27942ae90a3231e0ba333e02203deee8060fdc70230a7f5b4ad7d7bc3e628cbe219a886b84269eaeb81e26b4fe014104ae31c31bf91278d99b8377a35bbce5b27d9fff15456839e919453fc7b3f721f0ba403ff96c9deeb680e5fd341c0fc3a7b90da4631ee39560639db462e9cb850fffffffff0240420f00000000001976a914b0dcbf97eabf4404e31d952477ce822dadbe7e1088acc060d211000000001976a9146b1281eec25ab4e1e0793ff4e08ab1abb3409cd988ac0000000001000000010b6072b386d4a773235237f64c1126ac3b240c84b917a3909ba1c43ded5f51f4000000008c493046022100bb1ad26df930a51cce110cf44f7a48c3c561fd977500b1ae5d6b6fd13d0b3f4a022100c5b42951acedff14abba2736fd574bdb465f3e6f8da12e2c5303954aca7f78f3014104a7135bfe824c97ecc01ec7d7e336185c81e2aa2c41ab175407c09484ce9694b44953fcb751206564a9c24dd094d42fdbfdd5aad3e063ce6af4cfaaea4ea14fbbffffffff0140420f00000000001976a91439aa3d569e06a1d7926dc4be1193c99bf2eb9ee088ac00000000" |
|||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,19 @@ |
|||
{ |
|||
"hash": "60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053", |
|||
"size": 1704, |
|||
"height": 371337, |
|||
"merkleroot": "ee27b8fb782a5bfb99c975f0d4686440b9af9e16846603e5f2830e0b6fbf158a", |
|||
"tx": [ |
|||
"4547b14bc16db4184fa9f141d645627430dd3dfa662d0e6f418fba497091da75", |
|||
"a965dba2ed06827ed9a24f0568ec05b73c431bc7f0fb6913b144e62db7faa519", |
|||
"5e3ab18cb7ba3abc44e62fb3a43d4c8168d00cf0a2e0f8dbeb2636bb9a212d12", |
|||
"f022935ac7c4c734bd2c9c6a780f8e7280352de8bd358d760d0645b7fe734a93", |
|||
"ec063cc8025f9f30a6ed40fc8b1fe63b0cbd2ea2c62664eb26b365e6243828ca", |
|||
"02c16e3389320da3e77686d39773dda65a1ecdf98a2ef9cfb938c9f4b58f7a40" |
|||
], |
|||
"time": 1410464577, |
|||
"nonce": 0, |
|||
"bits": "1b364184", |
|||
"previousblockhash": "46a8b109fb016fa41abd17a19186ca78d39c60c020c71fcd2690320d47036f0d", |
|||
"block": "020162000d6f03470d329026cd1fc720c0609cd378ca8691a117bd1aa46f01fb09b1a8468a15bf6f0b0e83f2e5036684169eafb9406468d4f075c999fb5b2a78fbb827ee41fb11548441361b0000000001000000010000000000000000000000000000000000000000000000000000000000000000ffffffff380345bf09fabe6d6d980ba42120410de0554d42a5b5ee58167bcd86bf7591f429005f24da45fb51cf0800000000000000cdb1f1ff0e000000ffffffff01800c0c2a010000001976a914aa3750aa18b8a0f3f0590731e1fab934856680cf88ac00000000b3e64e02fff596209c498f1b18f798d62f216f11c8462bf3922319000000000003a979a636db2450363972d211aee67b71387a3daaa3051be0fd260c5acd4739cd52a418d29d8a0e56c8714c95a0dc24e1c9624480ec497fe2441941f3fee8f9481a3370c334178415c83d1d0c2deeec727c2330617a47691fc5e79203669312d100000000036fa40307b3a439538195245b0de56a2c1db6ba3a64f8bdd2071d00bc48c841b5e77b98e5c7d6f06f92dec5cf6d61277ecb9a0342406f49f34c51ee8ce4abd678038129485de14238bd1ca12cd2de12ff0e383aee542d90437cd664ce139446a00000000002000000d2ec7dfeb7e8f43fe77aba3368df95ac2088034420402730ee0492a2084217083411b3fc91033bfdeea339bc11b9efc986e161c703e07a9045338c165673f09940fb11548b54021b58cc9ae50601000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d0389aa050101062f503253482fffffffff010066f33caf050000232102b73438165461b826b30a46078f211aa005d1e7e430b1e0ed461678a5fe516c73ac000000000100000001ef2e86aa5f027e13d7fc1f0bd4a1fc677d698e42850680634ccd1834668ff320010000006b483045022100fcf5dc43afa85978a71e76a9f4c11cd6bf2a7d5677212f9001ad085d420a5d3a022068982e1e53e94fc6007cf8b60ff3919bcaf7f0b70fefb79112cb840777d8c7cf0121022b050b740dd02c1b4e1e7cdbffe6d836d987c9db4c4db734b58526f08942193bffffffff02004e7253000000001976a91435cb1f77e88e96fb3094d84e8d3b7789a092636d88ac00d4b7e8b00700001976a9146ca1f634daa4efc7871abab945c7cefd282b481f88ac0000000001000000010a6c24bbc92fd0ec32bb5b0a051c44eba0c1325f0b24d9523c109f8bb1281f49000000006a4730440220608577619fb3a0b826f09df5663ffbf121c8e0164f43b73d9affe2f9e4576bd0022040782c9a7df0a20afe1a7e3578bf27e1331c862253af21ced4fde5ef1b44b787012103e4f91ad831a87cc532249944bc7138a355f7d0aac25dc4737a8701181ce680a5ffffffff010019813f0d0000001976a91481db1aa49ebc6a71cad96949eb28e22af85eb0bd88ac0000000001000000017b82db0f644ecff378217d9b8dc0de8817eaf85ceefacab23bf344e2e495dca5010000006b483045022100f07ced6bfdbd6cdeb8b2c8fc92b9803f5798754b5b6c454c8f084198bea303f402205616f84d7ec882af9c34a3fd2457ca3fb81ec5a463a963a6e684edee427d4525012102c056b10494520dbd7b37e2e6bb8f72f98d73a609a926901221bfb114fa1d5a80ffffffff02f0501a22000000001976a914ca63ded8b23d0252158a3bdc816747ef89fb438988ac80b65ea1350700001976a914fb26a7c16ace531a8e7bbd925e46c67c3150c1c888ac000000000100000001c9bdba900e1579ebf4e44415fe8b9abec57a763f8c70a30604bea7fbe7c55d42000000006a47304402204ccbeeace0630e72102fdaf0836e41f8f6dcdde6a178f0fbc2d96a4d17a1df8f02207e4a91203a2abd87fdddee96510482ef96535741b6c17a1acae93c977ad248e5012103e0747583a342b76a5de9c21db138b9640d49b4f3b67a306d3b3f217416d49b55ffffffff020058850c020000001976a9144417c63a91208a02a5f46a0f7a2b806adc7d19a788ac0042dc06030000001976a9147b61c5adef0d559e5acf2901c2989294624b651988ac0000000001000000017c1423b198dfc3da37ae9a5fc11a3720e4343b3049d3b289b8285eb04595c04b000000006b483045022100b0c1cb9608bf644d7a8916bf61f36ced95bd045e97612804ca774f60e05e7bde022017c12255eecc474c8d8b05d0910013b2df8703af68212cf0962b6b8ee0e101ee01210341e154088c23b8ea943bca94c1d4f65361668a242b168522f00199365414b46affffffff01019891ad000000001976a91481db1aa49ebc6a71cad96949eb28e22af85eb0bd88ac00000000" |
|||
} |
@ -0,0 +1,18 @@ |
|||
{ |
|||
"hash": "545127eacc261629ae25ada99c7aadc1a929aed2da32f95ef866333f37c11e49", |
|||
"size": 1132, |
|||
"height": 900000, |
|||
"merkleroot": "11929e3e325f6346e9d24c0373dafbafcaaa7837aa862f33b7c529d457ca1229", |
|||
"tx": [ |
|||
"ad21fe3e94fd3da9a0920ed2fd112f7c805ac1b80274f4d999da3d2a5c6bd733", |
|||
"ea3b27388e968c413ef6af47be2843d649979e9b721331f593287b8d486be230", |
|||
"3b6b555a86471c5e5ee3d07838df04a6802f83b6f37c79922b86ef1983262d5e", |
|||
"026f93ffe84775b6c42b660944d25f7224c31b1175db837b664db32cd42e2300", |
|||
"7c274e298aa6feae7a0590dffca92d31b1f5f3697b26c6ceb477efc43f0afe39" |
|||
], |
|||
"time": 1449526456, |
|||
"nonce": 685998084, |
|||
"bits": "1b014ec5", |
|||
"previousblockhash": "93819e801bbdaec2698e3dda35e12be0a0004759c635924fda7f007a358848be", |
|||
"block": "03000000be4888357a007fda4f9235c6594700a0e02be135da3d8e69c2aebd1b809e81932912ca57d429c5b7332f86aa3778aacaaffbda73034cd2e946635f323e9e9211b8046656c54e011b0480e3280501000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6403a0bb0de4b883e5bda9e7a59ee4bb99e9b1bcfabe6d6d227c509f30b3ac49948323ce5974f89f6261ed913a701fc137bc08ead15179b940000000f09f909f4d696e6564206279206c6a6a38380000000000000000000000000000000000000000000000510000000176fa0795000000001976a914aa3750aa18b8a0f3f0590731e1fab934856680cf88ac5aa5893301000000015992d44c8d8790727c91055ce305e115373ff7fe32d632edc3f9939914b7d810000000006a47304402203ea789e265999b19b2155e4eb6135a50773d45836e1abb00a4959126c323e25d02207db24c9069683a6e4fc850a700717da08bf2c3ea80e8f3ee1ac75c1b702198800121033567eb9b5281b320bd8f20718b205e1808e7c0432d41991bdfad3eb5b53c49f9ffffffff02427e8837000000001976a914bc8d35412e239d91f9c95548afa15e22f094be3688ac0027b929000000001976a914b5e82238517f926b14467fbf8f90812b0eec8e5288ac000000000100000001ad3d610da30df966af2407b45bf0236a782f1e4444b829bf59da1679ceb16733000000006a47304402206b32468586635a1965fbb1c186799f1ccfce13549bd098845b97e75ea8bff473022021f35faf6e67428d51e58ed1895f9db2d40337d04e1b8819154c2bc71b0446af012102a740669302896fc4bdba32a951a67f95b3369fbc2ac97f1fda559999866d623bffffffff0245781300000000001976a9146f67216770c0af807e0597896a8c8ec306994e7b88ac80841e00000000001976a914b5e8223ec1e89b386cb5beb1c30cf165ac84e46388ac000000000100000001520f304eec49a1a9eeb0682da600b436a8dd43efc97ff4ed6ac2bcf0912e5caa000000006a473044022040218475e180db66cf71aa56668145b4f4d4d0a93b0e3777985039d87a53f881022047aaef5b4e262365c2dd2d7e1cbdf3016ff22468faef6104e4397540c199dfc6012103418a46f4534e7ec8a98146da6431550c370069777cacfdfbccc7a01f31abd1d0ffffffff02505bd425000000001976a9149f74e62f0f92663525050b56ad8b180048b4e80488ac408d1c1b000000001976a9149f7044d46304c187dc08d05864aeccb5a044e45588ac00000000010000000139c9bb7efca3fdd77ae18adf87614827d1c0bb1803a0d50ae42342e524ca99b7000000006a47304402205b75fd27c33c89346bc778d1369549b27f41ed0ded4947a19fb2884363a8ee7502206672bb1bd4e4a2a89cba62d1c5a93e1a6ae042f379e57380aebf14a693b42bea0121024f5b70c3309c77762c1b487f804c9666f5302545d7555d1808b63fdc9c17f840ffffffff01f3247d00000000001976a9149a20d4f533a7d7670cf14c77107dfd1eefddbd5388ac00000000" |
|||
} |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"hash": "d8a7c3e01e1e95bcee015e6fcc7583a2ca60b79e5a3aa0a171eddd344ada903d", |
|||
"size": 678, |
|||
"height": 19200, |
|||
"merkleroot": "88afdfdcc78f778f701835b62e432d3ba7d55b3e59ac4e7cab08d6bc49655c0f", |
|||
"tx": [ |
|||
"88afdfdcc78f778f701835b62e432d3ba7d55b3e59ac4e7cab08d6bc49655c0f" |
|||
], |
|||
"time": 1318066829, |
|||
"nonce": 0, |
|||
"bits": "1b00b269", |
|||
"previousblockhash": "000000000000b19f0ad5cd46859fe8c9662e8828d8a75ff6da73167ac09a9036", |
|||
"block": "0101010036909ac07a1673daf65fa7d828882e66c9e89f8546cdd50a9fb10000000000000f5c6549bcd608ab7c4eac593e5bd5a73b2d432eb63518708f778fc7dcdfaf888d1a904e69b2001b0000000001000000010000000000000000000000000000000000000000000000000000000000000000ffffffff35045dee091a014d522cfabe6d6dd8a7c3e01e1e95bcee015e6fcc7583a2ca60b79e5a3aa0a171eddd344ada903d0100000000000000ffffffff0160a0102a01000000434104f8bbe97ed2acbc5bba11c68f6f1a0313f918f3d3c0e8475055e351e3bf442f8c8dcee682d2457bdc5351b70dd9e34026766eba18b06eaee2e102efd1ab634667ac00000000a903ef9de1918e4b44f6176a30c0e7c7e3439c96fb597327473d00000000000005050ac4a1a1e1bce0c48e555b1a9f935281968c72d6379b24729ca0425a3fc3cb433cd348b35ea22806cf21c7b146489aef6989551eb5ad2373ab6121060f30341d648757c0217d43e66c57eaed64fc1820ec65d157f33b741965183a5e0c8506ac2602dfe2f547012d1cc75004d48f97aba46bd9930ff285c9f276f5bd09f356df19724579d65ec7cb62bf97946dfc6fb0e3b2839b7fdab37cdb60e55122d35b0000000000000000000100000008be13295c03e67cb70d00dae81ea06e78b9014e5ceb7d9ba504000000000000e0fd42db8ef6d783f079d126bea12e2d10c104c0927cd68f954d856f9e8111e59a23904e5dee091a1c6550860101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff080469b2001b010152ffffffff0100f2052a0100000043410489fe91e62847575c98deeab020f65fdff17a3a870ebb05820b414f3d8097218ec9a65f1e0ae0ac35af7247bd79ed1f2a24675fffb5aa6f9620e1920ad4bf5aa6ac00000000" |
|||
} |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"hash": "000000000000122ff239e71146bf57aee28ad913931d672cd124255e91351660", |
|||
"size": 475, |
|||
"height": 19204, |
|||
"merkleroot": "45d5bc5330dad21dd4dcf0daadefef4ba826fe81e2dd2fa38a4a49ea06c97b1d", |
|||
"tx": [ |
|||
"7752b6a596641bd90ae71d1bc054f3dd1ad36ce3fe7e7d3292ff8594feafb8a9", |
|||
"499dad7cd9e737c9f2f10bc4b3930b566d82288a8c02b68a50d2cf2694868bdd" |
|||
], |
|||
"time": 1318073606, |
|||
"nonce": 3373003561, |
|||
"bits": "1b00b269", |
|||
"previousblockhash": "07d6d85d2f33fae0b52d84a90757d1600fdb3f2cf2f31f2a32eef59172245af6", |
|||
"block": "01000100f65a247291f5ee322a1ff3f22c3fdb0f60d15707a9842db5e0fa332f5dd8d6071d7bc906ea494a8aa32fdde281fe26a84befefaddaf0dcd41dd2da3053bcd5450635904e69b2001b29f30bc90201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff080469b2001b027829ffffffff0100f2052a0100000043410439cf5bc2e4b0d555178b3d19fa82d59aa998cc082086f874928af6e70c1093b300b6371d093ac9d41393e11907ed17d2489405e220a6f08feeecbce9f6cfcc0bac00000000010000000148efa1ba7512bbd538033b798da1064e724e21739f6bd8ea0c28e3d0d53136d6010000008c49304602210095cf1a495623ed7794746b7b0f2daa70a9783f635e24991487e8a6869b553c9b0221009dec7919115c3a84f03236c8aea6e175e8dd9ee3a6daa1f6c56ac1d6246ec5da014104d483cffe3907aefdb9a20dab73dd4c83f6d14d26bd9d21aeccd33b0be2068e4832fea66110606198728413ad88a6dd642bdd6ff72aefd79732a2375c3129f1fcffffffff0220ab6136000000001976a9141b5a80636dfa8c4e78c1d1150a2ba961d704911388ac0065cd1d000000001976a9143f47c122f3a71e70cb3a4c9d215e5ce7b740b96a88ac00000000" |
|||
} |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"hash": "bea68724bfcdc5d35bf9bbf9cb6680e196e5661afe95b2a205e74a2fe175ac79", |
|||
"size": 443, |
|||
"height": 1200000, |
|||
"merkleroot": "504b073c16d872d24f2c3de8a4c2c76d08df5056f3a4a8d0e32ff4220215a250", |
|||
"tx": [ |
|||
"6aaad9725ae7beb40d80dac9c5300a8e1cf8783adb0ea41da4988b3476bda9b8", |
|||
"4a949402995c11b3306c0c91fd85edf0d3eb8dee4bf6bd07a241fa170156cd3c" |
|||
], |
|||
"time": 1463612841, |
|||
"nonce": 0, |
|||
"bits": "1c0a4691", |
|||
"previousblockhash": "438b564171da6fbbe6fd9d52c16ea2b1aa8c169951822225cf097d5da7cdba76", |
|||
"block": "0300000076bacda75d7d09cf2522825199168caab1a26ec1529dfde6bb6fda7141568b4350a2150222f42fe3d0a8a4f35650df086dc7c2a4e83d2c4fd272d8163c074b50a9f53c5791460a1c000000000202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff020000ffffffff0100000000000000000000000000a9f53c570200000001a40cad8a9afe2888f746d762cb36649b5afd4e8ce4468fd8d08fc296d26dc4840100000048473044022036392ee6eb58c5a9a2a681692cabdc2b00166c374cfb711055bc2c4d6c61a1d40220475728eed260bf972ef44909f0d6fa282f17e92b5e57ee383c7171e8a3baee1f01ffffffff030000000000000000000056b12a38720000232102bee8ce24a99260fbb6c10f0b904498fa71ec08e51b531878d3f6568ef09acb91ac0ad6b22a38720000232102bee8ce24a99260fbb6c10f0b904498fa71ec08e51b531878d3f6568ef09acb91ac00000000a9f53c57473045022100fe801bae06c9db3076fad2f72930f76dbe1cae29a162447b13d0df749e5913df02203621013f87da4dbca08702d8c7975f702bad9df40902038b93e622a0dd9c0896" |
|||
} |
@ -0,0 +1,20 @@ |
|||
{ |
|||
"hash": "4889bb7d1ba24cc66c2d903f6643b0ade243aca5101a8aff87ab4c2ab2a15ec5", |
|||
"size": 1560, |
|||
"height": 80000, |
|||
"merkleroot": "193313cfa4d8a4bc15fb5526b69a87c922e0f6520295f66165358f0af6b5d637", |
|||
"tx": [ |
|||
"ad01e368a301b855d5f4499bc787b161428d6994c4847c0b2813950630a73950", |
|||
"1799481d7fed61c029159d314f75f3d6f69a7f8c237443470394085307802782", |
|||
"8db4b2c62fca987462c482d24ce0b78d2a3dd3928d5d99112ccad75deb6ff7de", |
|||
"ab0a1e66e54c737be6ea2be2c61cd55879d33c0fc5d35aa6389487e06c809cfc", |
|||
"1bb3854ed7fe9905b5637d405cd0715e5cb6f5fe233304a1588c53bdcf60f593", |
|||
"08d3ccf77f30e62d8773669adea730698806516239933ac7c4285bcacdb37989", |
|||
"19cbdc4acfb07dc29c73f039d8f5db967ce30c0667fda60babc700a7c53c0b5f" |
|||
], |
|||
"time": 1396181239, |
|||
"nonce": 1368399360, |
|||
"bits": "1c0cc111", |
|||
"previousblockhash": "e34cfbf84095c64276ee098de50c3a1f863df9336968e9fb8a973bdd52e3ed04", |
|||
"block": "0200000004ede352dd3b978afbe9686933f93d861f3a0ce58d09ee7642c69540f8fb4ce337d6b5f60a8f356561f6950252f6e022c9879ab62655fb15bca4d8a4cf133319f708385311c10c1c001e90510701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2703803801062f503253482f04f608385308f800f159010000000d2f7374726174756d506f6f6c2f000000000100e63572180900001976a9148d41bc27ab2cc999338750edd4b0012bdb36f70288ac0000000001000000014b1a8085e29ca8ef2bf1de5f0637c6ef597b67223a087458e51e21168a0e44a3000000006b48304502200b781c255481e90f0e1d2fedbc1ffb42562434c324566444da8718a8a2c5182d022100f50faa7a9f7b90b4b805050c9731a79fb9c599ddfb3d84449d0cff7ee216bf59012103d7ab8ea88d09589410bdb93cd466d92f56985a3cff6d74dce3f033500135f0c5ffffffff02d72ea96581330e001976a91422758e8f790ea0e4ab87ad3990e8af86c77375c088ac1c1cab70190000001976a91434e880ed4cb32ebb1e0842b4f05efe562724f08788ac000000000100000001616c5b1a7ee823fa2d5347011b34e1ea027f9494823d37fb175eece8f852f987000000006a473044022000be9cf6677d879d170c597b8a465137577119ebc7d01773dc13df7af7e0bf1102202acfce90f478c0d179ab98d708f1e24f6dab4fe60c75893f8bad12991b30f41301210355dad820f63f1c315dc16c5afd9782e4d0b225ea29320a85576bc2c82fde6e7effffffff02ceb618fa97ac10001976a914e14548bfd2e14e0cabaf535c7c80a227238b35e188ac1c1cab70190000001976a914d2046a1ad1dbc32e69dae4da0a8730379105936e88ac000000000100000001a6b3081431b43c3247df88b3b6d123d2f2d7ba2095c6ef4f6532feb2c45f9210010000006b4830450221008fb902cc4130bae26439c47c13467a7d8a8c52ac2d88a200548f1e8f8b100b910220125b45cee0765389a59d4cca65482bdf79d3bc8fdaa5a0142e7829e4a2568124012103cdece1576249c8e05fb0aa2cbe61aa959330ff2f9e3c5cd2e5152e90650d9386ffffffff02bbba56d0d88606001976a91407499b20688a0b61b4a526681647de739dab818e88ac1c1cab70190000001976a9147085556af12556138277188e3958a869eeced02088ac000000000100000001fc9c806ce0879438a65ad3c50f3cd37958d51cc6e22beae67b734ce5661e0aab000000006c493046022100dca959b02a4dde588b3e5c3e71877797b97d7094a82cdd6b6b52c3d04a8c17c3022100938b2f70eed007d20ef9d7d055fc9b8785e71e3f0981558503fb3635b08aa6d40121039d216b71bad34246ceff262afe6df520761fc696fd9862c3f2f7e337ad93d881ffffffff0202386cc4f57e06001976a914ee343e816e6782262c3f6b1b9ec8f8c17d47a88c88acb9a1f405e30700001976a914ba81e33df7ba3d18728c6c206f8ad0b30b83b71988ac00000000010000000193f560cfbd538c58a1043323fef5b65c5e71d05c407d63b50599fed74e85b31b000000006a4730440220153f0a0a16e13943c4869e8f768c64e9f1844d14823f80878a6e44752a041c49022036ec13a307bafee74387048c3772cfb5ebdc138d70d6b4c256788a86db93ab5801210281232e155b37ebd64759ee4983962e9f8ccfd95e302d828de1406549e7c327a4ffffffff029014fad0166506001976a914b05959ea5dd831fd082488298466c9307a46f55b88ac72427cedde1900001976a914c2e3e90990f452c19ccef5df1cc3711c2e5d448288ac0000000001000000018979b3cdca5b28c4c73a9339625106886930a7de9a6673872de6307ff7ccd308000000006b483045022100ec50258bfec642e6c986192f338b7a1eec84c872d9b51ccc6f1c7329da20af77022047a6836d7c5f416c2eef6ef59fae9cc627ff80882897fe3eabd775e2a4a08533012102240bb70ae679cb25d60e2e0f90f98017eac7b6abbf1e00797ef930f02f0b98eeffffffff029e75ab66cd6306001976a9144c9ef3b178febefc62a0067e67e8434afe864a6788acf2bd5864490100001976a9143cde6d950e730b199c5857564afe7f222e139ead88ac00000000" |
|||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,68 @@ |
|||
# |
|||
# Tests of lib/hash.py |
|||
# |
|||
|
|||
import pytest |
|||
|
|||
import lib.hash as lib_hash |
|||
|
|||
|
|||
def test_sha256(): |
|||
assert lib_hash.sha256(b'sha256') == b'][\t\xf6\xdc\xb2\xd5:_\xff\xc6\x0cJ\xc0\xd5_\xab\xdfU`i\xd6c\x15E\xf4*\xa6\xe3P\x0f.' |
|||
with pytest.raises(TypeError): |
|||
lib_hash.sha256('sha256') |
|||
|
|||
def ripemd160(x): |
|||
assert lib_hash.ripemd160(b'ripemd160') == b'\x903\x91\xa1\xc0I\x9e\xc8\xdf\xb5\x1aSK\xa5VW\xf9|W\xd5' |
|||
with pytest.raises(TypeError): |
|||
lib_hash.ripemd160('ripemd160') |
|||
|
|||
def test_double_sha256(): |
|||
assert lib_hash.double_sha256(b'double_sha256') == b'ksn\x8e\xb7\xb9\x0f\xf6\xd9\xad\x88\xd9#\xa1\xbcU(j1Bx\xce\xd5;s\xectL\xe7\xc5\xb4\x00' |
|||
|
|||
def test_hmac_sha512(): |
|||
assert lib_hash.hmac_sha512(b'key', b'message') == b"\xe4w8M|\xa2)\xdd\x14&\xe6Kc\xeb\xf2\xd3n\xbdm~f\x9ag5BNr\xeal\x01\xd3\xf8\xb5n\xb3\x9c6\xd8#/T'\x99\x9b\x8d\x1a?\x9c\xd1\x12\x8f\xc6\x9fMu\xb44!h\x10\xfa6~\x98" |
|||
|
|||
def test_hash160(): |
|||
assert lib_hash.hash160(b'hash_160') == b'\xb3\x96\x94\xfc\x978R\xa7)XqY\xbb\xdc\xeb\xac\xa7%\xb8$' |
|||
|
|||
def test_hash_to_hex_str(): |
|||
assert lib_hash.hash_to_hex_str(b'hash_to_str') == '7274735f6f745f68736168' |
|||
|
|||
def test_hex_str_to_hash(): |
|||
assert lib_hash.hex_str_to_hash('7274735f6f745f68736168') == b'hash_to_str' |
|||
|
|||
def test_Base58_char_value(): |
|||
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' |
|||
for value, c in enumerate(chars): |
|||
assert lib_hash.Base58.char_value(c) == value |
|||
for c in (' ', 'I', '0', 'l', 'O'): |
|||
with pytest.raises(lib_hash.Base58Error): |
|||
lib_hash.Base58.char_value(c) |
|||
|
|||
def test_Base58_decode(): |
|||
with pytest.raises(TypeError): |
|||
lib_hash.Base58.decode(b'foo') |
|||
with pytest.raises(lib_hash.Base58Error): |
|||
lib_hash.Base58.decode('') |
|||
assert lib_hash.Base58.decode('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') == b'\x00\x01\x11\xd3\x8e_\xc9\x07\x1f\xfc\xd2\x0bJv<\xc9\xaeO%+\xb4\xe4\x8f\xd6j\x83^%*\xda\x93\xffH\rm\xd4=\xc6*d\x11U\xa5' |
|||
assert lib_hash.Base58.decode('3i37NcgooY8f1S') == b'0123456789' |
|||
|
|||
def test_Base58_encode(): |
|||
with pytest.raises(TypeError): |
|||
lib_hash.Base58.encode('foo') |
|||
assert lib_hash.Base58.encode(b'') == '' |
|||
assert lib_hash.Base58.encode(b'\0') == '1' |
|||
assert lib_hash.Base58.encode(b'0123456789') == '3i37NcgooY8f1S' |
|||
|
|||
def test_Base58_decode_check(): |
|||
with pytest.raises(TypeError): |
|||
lib_hash.Base58.decode_check(b'foo') |
|||
assert lib_hash.Base58.decode_check('4t9WKfuAB8') == b'foo' |
|||
with pytest.raises(lib_hash.Base58Error): |
|||
lib_hash.Base58.decode_check('4t9WKfuAB9') |
|||
|
|||
def test_Base58_encode_check(): |
|||
with pytest.raises(TypeError): |
|||
lib_hash.Base58.encode_check('foo') |
|||
assert lib_hash.Base58.encode_check(b'foo') == '4t9WKfuAB8' |
@ -0,0 +1,131 @@ |
|||
# Test of compaction code in server/db.py |
|||
|
|||
import array |
|||
from collections import defaultdict |
|||
from os import environ, urandom |
|||
from struct import pack |
|||
import random |
|||
|
|||
from lib.hash import hash_to_str |
|||
from server.env import Env |
|||
from server.db import DB |
|||
|
|||
|
|||
def create_histories(db, hashX_count=100): |
|||
'''Creates a bunch of random transaction histories, and write them |
|||
to disk in a series of small flushes.''' |
|||
hashXs = [urandom(db.coin.HASHX_LEN) for n in range(hashX_count)] |
|||
mk_array = lambda : array.array('I') |
|||
histories = {hashX : mk_array() for hashX in hashXs} |
|||
this_history = defaultdict(mk_array) |
|||
tx_num = 0 |
|||
while hashXs: |
|||
hash_indexes = set(random.randrange(len(hashXs)) |
|||
for n in range(1 + random.randrange(4))) |
|||
for index in hash_indexes: |
|||
histories[hashXs[index]].append(tx_num) |
|||
this_history[hashXs[index]].append(tx_num) |
|||
|
|||
tx_num += 1 |
|||
# Occasionally flush and drop a random hashX if non-empty |
|||
if random.random() < 0.1: |
|||
db.flush_history(this_history) |
|||
this_history.clear() |
|||
index = random.randrange(0, len(hashXs)) |
|||
if histories[hashXs[index]]: |
|||
del hashXs[index] |
|||
|
|||
return histories |
|||
|
|||
|
|||
def check_hashX_compaction(db): |
|||
db.max_hist_row_entries = 40 |
|||
row_size = db.max_hist_row_entries * 4 |
|||
full_hist = array.array('I', range(100)).tobytes() |
|||
hashX = urandom(db.coin.HASHX_LEN) |
|||
pairs = ((1, 20), (26, 50), (56, 30)) |
|||
|
|||
cum = 0 |
|||
hist_list = [] |
|||
hist_map = {} |
|||
for flush_count, count in pairs: |
|||
key = hashX + pack('>H', flush_count) |
|||
hist = full_hist[cum * 4: (cum+count) * 4] |
|||
hist_map[key] = hist |
|||
hist_list.append(hist) |
|||
cum += count |
|||
|
|||
write_items = [] |
|||
keys_to_delete = set() |
|||
write_size = db._compact_hashX(hashX, hist_map, hist_list, |
|||
write_items, keys_to_delete) |
|||
# Check results for sanity |
|||
assert write_size == len(full_hist) |
|||
assert len(write_items) == 3 |
|||
assert len(keys_to_delete) == 3 |
|||
assert len(hist_map) == len(pairs) |
|||
for n, item in enumerate(write_items): |
|||
assert item == (hashX + pack('>H', n), |
|||
full_hist[n * row_size: (n + 1) * row_size]) |
|||
for flush_count, count in pairs: |
|||
assert hashX + pack('>H', flush_count) in keys_to_delete |
|||
|
|||
# Check re-compaction is null |
|||
hist_map = {key: value for key, value in write_items} |
|||
hist_list = [value for key, value in write_items] |
|||
write_items.clear() |
|||
keys_to_delete.clear() |
|||
write_size = db._compact_hashX(hashX, hist_map, hist_list, |
|||
write_items, keys_to_delete) |
|||
assert write_size == 0 |
|||
assert len(write_items) == 0 |
|||
assert len(keys_to_delete) == 0 |
|||
assert len(hist_map) == len(pairs) |
|||
|
|||
# Check re-compaction adding a single tx writes the one row |
|||
hist_list[-1] += array.array('I', [100]).tobytes() |
|||
write_size = db._compact_hashX(hashX, hist_map, hist_list, |
|||
write_items, keys_to_delete) |
|||
assert write_size == len(hist_list[-1]) |
|||
assert write_items == [(hashX + pack('>H', 2), hist_list[-1])] |
|||
assert len(keys_to_delete) == 1 |
|||
assert write_items[0][0] in keys_to_delete |
|||
assert len(hist_map) == len(pairs) |
|||
|
|||
|
|||
def check_written(db, histories): |
|||
for hashX, hist in histories.items(): |
|||
db_hist = array.array('I', db.get_history_txnums(hashX, limit=None)) |
|||
assert hist == db_hist |
|||
|
|||
def compact_history(db): |
|||
'''Synchronously compact the DB history.''' |
|||
db.first_sync = False |
|||
db.comp_cursor = 0 |
|||
|
|||
db.comp_flush_count = max(db.comp_flush_count, 1) |
|||
limit = 5 * 1000 |
|||
|
|||
write_size = 0 |
|||
while db.comp_cursor != -1: |
|||
write_size += db._compact_history(limit) |
|||
assert write_size != 0 |
|||
|
|||
def run_test(db_dir): |
|||
environ.clear() |
|||
environ['DB_DIRECTORY'] = db_dir |
|||
environ['DAEMON_URL'] = '' |
|||
env = Env() |
|||
db = DB(env) |
|||
# Test abstract compaction |
|||
check_hashX_compaction(db) |
|||
# Now test in with random data |
|||
histories = create_histories(db) |
|||
check_written(db, histories) |
|||
compact_history(db) |
|||
check_written(db, histories) |
|||
|
|||
def test_compaction(tmpdir): |
|||
db_dir = str(tmpdir) |
|||
print('Temp dir: {}'.format(db_dir)) |
|||
run_test(db_dir) |
@ -0,0 +1,282 @@ |
|||
# Tests of server/env.py |
|||
|
|||
import os |
|||
import random |
|||
|
|||
import pytest |
|||
|
|||
from server.env import Env, NetIdentity |
|||
import lib.coins as lib_coins |
|||
|
|||
|
|||
BASE_DAEMON_URL = 'http://username:password@hostname:321/' |
|||
BASE_DB_DIR = '/some/dir' |
|||
|
|||
base_environ = { |
|||
'DB_DIRECTORY': BASE_DB_DIR, |
|||
'DAEMON_URL': BASE_DAEMON_URL, |
|||
} |
|||
|
|||
def setup_base_env(): |
|||
os.environ.clear() |
|||
os.environ.update(base_environ) |
|||
|
|||
def assert_required(env_var): |
|||
setup_base_env() |
|||
os.environ.pop(env_var, None) |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
|
|||
def assert_default(env_var, attr, default): |
|||
setup_base_env() |
|||
e = Env() |
|||
assert getattr(e, attr) == default |
|||
os.environ[env_var] = 'foo' |
|||
e = Env() |
|||
assert getattr(e, attr) == 'foo' |
|||
|
|||
def assert_integer(env_var, attr, default=''): |
|||
if default != '': |
|||
e = Env() |
|||
assert getattr(e, attr) == default |
|||
value = random.randrange(5, 2000) |
|||
os.environ[env_var] = str(value) + '.1' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ[env_var] = str(value) |
|||
e = Env() |
|||
assert getattr(e, attr) == value |
|||
|
|||
def assert_boolean(env_var, attr, default): |
|||
e = Env() |
|||
assert getattr(e, attr) == default |
|||
os.environ[env_var] = 'foo' |
|||
e = Env() |
|||
assert getattr(e, attr) == True |
|||
os.environ[env_var] = '' |
|||
e = Env() |
|||
assert getattr(e, attr) == False |
|||
|
|||
def test_minimal(): |
|||
setup_base_env() |
|||
Env() |
|||
|
|||
def test_DB_DIRECTORY(): |
|||
assert_required('DB_DIRECTORY') |
|||
setup_base_env() |
|||
e = Env() |
|||
assert e.db_dir == BASE_DB_DIR |
|||
|
|||
def test_DAEMON_URL(): |
|||
assert_required('DAEMON_URL') |
|||
setup_base_env() |
|||
e = Env() |
|||
assert e.daemon_url == BASE_DAEMON_URL |
|||
|
|||
def test_COIN_NET(): |
|||
'''Test COIN and NET defaults and redirection.''' |
|||
setup_base_env() |
|||
e = Env() |
|||
assert e.coin == lib_coins.Bitcoin |
|||
os.environ['NET'] = 'testnet' |
|||
e = Env() |
|||
assert e.coin == lib_coins.BitcoinTestnet |
|||
os.environ.pop('NET') |
|||
os.environ['COIN'] = 'Litecoin' |
|||
e = Env() |
|||
assert e.coin == lib_coins.Litecoin |
|||
os.environ['NET'] = 'testnet' |
|||
e = Env() |
|||
assert e.coin == lib_coins.LitecoinTestnet |
|||
|
|||
def test_CACHE_MB(): |
|||
assert_integer('CACHE_MB', 'cache_MB', 1200) |
|||
|
|||
def test_HOST(): |
|||
assert_default('HOST', 'host', 'localhost') |
|||
|
|||
def test_REORG_LIMIT(): |
|||
assert_integer('REORG_LIMIT', 'reorg_limit', lib_coins.Bitcoin.REORG_LIMIT) |
|||
|
|||
def test_TCP_PORT(): |
|||
assert_integer('TCP_PORT', 'tcp_port', None) |
|||
|
|||
def test_SSL_PORT(): |
|||
# Requires both SSL_CERTFILE and SSL_KEYFILE to be set |
|||
os.environ['SSL_PORT'] = '50002' |
|||
os.environ['SSL_CERTFILE'] = 'certfile' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ.pop('SSL_CERTFILE') |
|||
os.environ['SSL_KEYFILE'] = 'keyfile' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['SSL_CERTFILE'] = 'certfile' |
|||
os.environ.pop('SSL_PORT') |
|||
assert_integer('SSL_PORT', 'ssl_port', None) |
|||
|
|||
def test_RPC_PORT(): |
|||
assert_integer('RPC_PORT', 'rpc_port', 8000) |
|||
|
|||
def test_MAX_SUBSCRIPTIONS(): |
|||
assert_integer('MAX_SUBSCRIPTIONS', 'max_subscriptions', 10000) |
|||
|
|||
def test_LOG_SESSIONS(): |
|||
assert_integer('LOG_SESSIONS', 'log_sessions', 3600) |
|||
|
|||
def test_DONATION_ADDRESS(): |
|||
assert_default('DONATION_ADDRESS', 'donation_address', '') |
|||
|
|||
def test_DB_ENGINE(): |
|||
assert_default('DB_ENGINE', 'db_engine', 'leveldb') |
|||
|
|||
def test_MAX_SEND(): |
|||
assert_integer('MAX_SEND', 'max_send', 1000000) |
|||
|
|||
def test_MAX_SUBS(): |
|||
assert_integer('MAX_SUBS', 'max_subs', 250000) |
|||
|
|||
def test_MAX_SESSION_SUBS(): |
|||
assert_integer('MAX_SESSION_SUBS', 'max_session_subs', 50000) |
|||
|
|||
def test_BANDWIDTH_LIMIT(): |
|||
assert_integer('BANDWIDTH_LIMIT', 'bandwidth_limit', 2000000) |
|||
|
|||
def test_SESSION_TIMEOUT(): |
|||
assert_integer('SESSION_TIMEOUT', 'session_timeout', 600) |
|||
|
|||
def test_BANNER_FILE(): |
|||
e = Env() |
|||
assert e.banner_file is None |
|||
assert e.tor_banner_file is None |
|||
os.environ['BANNER_FILE'] = 'banner_file' |
|||
e = Env() |
|||
assert e.banner_file == 'banner_file' |
|||
assert e.tor_banner_file == 'banner_file' |
|||
os.environ['TOR_BANNER_FILE'] = 'tor_banner_file' |
|||
e = Env() |
|||
assert e.banner_file == 'banner_file' |
|||
assert e.tor_banner_file == 'tor_banner_file' |
|||
|
|||
def test_ANON_LOGS(): |
|||
assert_boolean('ANON_LOGS', 'anon_logs', False) |
|||
|
|||
def test_PEER_DISCOVERY(): |
|||
assert_boolean('PEER_DISCOVERY', 'peer_discovery', True) |
|||
|
|||
def test_PEER_ANNOUNCE(): |
|||
assert_boolean('PEER_ANNOUNCE', 'peer_announce', True) |
|||
|
|||
def test_FORCE_PROXY(): |
|||
assert_boolean('FORCE_PROXY', 'force_proxy', False) |
|||
|
|||
def test_TOR_PROXY_HOST(): |
|||
assert_default('TOR_PROXY_HOST', 'tor_proxy_host', 'localhost') |
|||
|
|||
def test_TOR_PROXY_PORT(): |
|||
assert_integer('TOR_PROXY_PORT', 'tor_proxy_port', None) |
|||
|
|||
def test_IRC(): |
|||
assert_boolean('IRC', 'irc', False) |
|||
|
|||
def test_IRC_NICK(): |
|||
assert_default('IRC_NICK', 'irc_nick', None) |
|||
|
|||
def test_clearnet_identity(): |
|||
os.environ['REPORT_TCP_PORT'] = '456' |
|||
e = Env() |
|||
assert len(e.identities) == 0 |
|||
os.environ['REPORT_HOST'] = '8.8.8.8' |
|||
e = Env() |
|||
assert len(e.identities) == 1 |
|||
assert e.identities[0].host == '8.8.8.8' |
|||
os.environ['REPORT_HOST'] = 'localhost' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST'] = '' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST'] = '127.0.0.1' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST'] = '0.0.0.0' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST'] = '224.0.0.2' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST'] = '$HOST' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
# Accept private IP, unless IRC or PEER_ANNOUNCE |
|||
os.environ.pop('IRC', None) |
|||
os.environ['PEER_ANNOUNCE'] = '' |
|||
os.environ['REPORT_HOST'] = '192.168.0.1' |
|||
os.environ['SSL_CERTFILE'] = 'certfile' |
|||
os.environ['SSL_KEYFILE'] = 'keyfile' |
|||
Env() |
|||
os.environ['IRC'] = 'OK' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ.pop('IRC', None) |
|||
os.environ['PEER_ANNOUNCE'] = 'OK' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_SSL_PORT'] = os.environ['REPORT_TCP_PORT'] |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_SSL_PORT'] = '457' |
|||
os.environ['REPORT_HOST'] = 'foo.com' |
|||
e = Env() |
|||
assert len(e.identities) == 1 |
|||
ident = e.identities[0] |
|||
assert ident.host == 'foo.com' |
|||
assert ident.tcp_port == 456 |
|||
assert ident.ssl_port == 457 |
|||
assert ident.nick_suffix == '' |
|||
|
|||
def test_tor_identity(): |
|||
tor_host = 'something.onion' |
|||
os.environ.pop('REPORT_HOST', None) |
|||
os.environ.pop('REPORT_HOST_TOR', None) |
|||
e = Env() |
|||
assert len(e.identities) == 0 |
|||
os.environ['REPORT_HOST_TOR'] = 'foo' |
|||
os.environ['REPORT_SSL_PORT_TOR'] = '123' |
|||
os.environ['TCP_PORT'] = '456' |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST_TOR'] = tor_host |
|||
e = Env() |
|||
assert len(e.identities) == 1 |
|||
ident = e.identities[0] |
|||
assert ident.host == tor_host |
|||
assert ident.tcp_port == 456 |
|||
assert ident.ssl_port == 123 |
|||
assert ident.nick_suffix == '_tor' |
|||
os.environ['REPORT_TCP_PORT_TOR'] = os.environ['REPORT_SSL_PORT_TOR'] |
|||
with pytest.raises(Env.Error): |
|||
Env() |
|||
os.environ['REPORT_HOST'] = 'foo.com' |
|||
os.environ['TCP_PORT'] = '456' |
|||
os.environ['SSL_PORT'] = '789' |
|||
os.environ['REPORT_TCP_PORT'] = '654' |
|||
os.environ['REPORT_SSL_PORT'] = '987' |
|||
os.environ['SSL_CERTFILE'] = 'certfile' |
|||
os.environ['SSL_KEYFILE'] = 'keyfile' |
|||
os.environ.pop('REPORT_TCP_PORT_TOR', None) |
|||
os.environ.pop('REPORT_SSL_PORT_TOR', None) |
|||
e = Env() |
|||
assert len(e.identities) == 2 |
|||
ident = e.identities[1] |
|||
assert ident.host == tor_host |
|||
assert ident.tcp_port == 654 |
|||
assert ident.ssl_port == 987 |
|||
os.environ['REPORT_TCP_PORT_TOR'] = '234' |
|||
os.environ['REPORT_SSL_PORT_TOR'] = '432' |
|||
e = Env() |
|||
assert len(e.identities) == 2 |
|||
ident = e.identities[1] |
|||
assert ident.host == tor_host |
|||
assert ident.tcp_port == 234 |
|||
assert ident.ssl_port == 432 |
@ -0,0 +1,68 @@ |
|||
# Copyright (c) 2017, the ElectrumX authors |
|||
# |
|||
# All rights reserved. |
|||
# |
|||
# The MIT License (MIT) |
|||
# |
|||
# Permission is hereby granted, free of charge, to any person obtaining |
|||
# a copy of this software and associated documentation files (the |
|||
# "Software"), to deal in the Software without restriction, including |
|||
# without limitation the rights to use, copy, modify, merge, publish, |
|||
# distribute, sublicense, and/or sell copies of the Software, and to |
|||
# permit persons to whom the Software is furnished to do so, subject to |
|||
# the following conditions: |
|||
# |
|||
# The above copyright notice and this permission notice shall be |
|||
# included in all copies or substantial portions of the Software. |
|||
# |
|||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
# and warranty status of this software. |
|||
|
|||
import json |
|||
import os |
|||
from binascii import unhexlify |
|||
|
|||
import pytest |
|||
|
|||
from lib.coins import Coin |
|||
from lib.hash import hex_str_to_hash |
|||
|
|||
BLOCKS_DIR = os.path.join( |
|||
os.path.dirname(os.path.realpath(__file__)), 'blocks') |
|||
|
|||
# Find out which db engines to test |
|||
# Those that are not installed will be skipped |
|||
blocks = [] |
|||
|
|||
for name in os.listdir(BLOCKS_DIR): |
|||
try: |
|||
name_parts = name.split("_") |
|||
coin = Coin.lookup_coin_class(name_parts[0], name_parts[1]) |
|||
with open(os.path.join(BLOCKS_DIR, name)) as f: |
|||
blocks.append((coin, json.load(f))) |
|||
except Exception as e: |
|||
blocks.append(pytest.fail(name)) |
|||
|
|||
|
|||
@pytest.fixture(params=blocks) |
|||
def block_details(request): |
|||
return request.param |
|||
|
|||
|
|||
def test_block(block_details): |
|||
coin, block_info = block_details |
|||
|
|||
block = unhexlify(block_info['block']) |
|||
h, txs = coin.block_full(block, block_info['height']) |
|||
|
|||
assert coin.header_hash(h) == hex_str_to_hash(block_info['hash']) |
|||
assert coin.header_prevhash(h) == hex_str_to_hash(block_info['previousblockhash']) |
|||
for n, tx in enumerate(txs): |
|||
_, txid = tx |
|||
assert txid == hex_str_to_hash(block_info['tx'][n]) |
@ -0,0 +1,397 @@ |
|||
# |
|||
# Tests of wallet/bip32.py |
|||
# |
|||
|
|||
import pytest |
|||
|
|||
import wallet.bip32 as bip32 |
|||
from lib.coins import Bitcoin, CoinError |
|||
from lib.hash import Base58 |
|||
|
|||
|
|||
MXPRV = 'xprv9s21ZrQH143K2gMVrSwwojnXigqHgm1khKZGTCm7K8w4PmuDEUrudk11ZBxhGPUiUeVcrfGLoZmt8rFNRDLp18jmKMcVma89z7PJd2Vn7R9' |
|||
MPRIVKEY = b';\xf4\xbfH\xd20\xea\x94\x01_\x10\x1b\xc3\xb0\xff\xc9\x17$?K\x02\xe5\x82R\xe5\xb3A\xdb\x87&E\x00' |
|||
MXPUB = 'xpub661MyMwAqRbcFARxxUUxAsjGGifn6Djc4YUsFbAisUU3GaEMn2BABYKVQTHrDtwvSfgY2bK8aFGyCNmB52SKjkFGP18sSRTNn1sCeez7Utd' |
|||
|
|||
mpubkey, mpubcoin = bip32.from_extended_key_string(MXPUB) |
|||
mprivkey, mprivcoin = bip32.from_extended_key_string(MXPRV) |
|||
|
|||
|
|||
def test_from_extended_key(): |
|||
# Tests the failure modes of from_extended_key. |
|||
with pytest.raises(TypeError): |
|||
bip32._from_extended_key('') |
|||
with pytest.raises(ValueError): |
|||
bip32._from_extended_key(b'') |
|||
with pytest.raises(CoinError): |
|||
bip32._from_extended_key(bytes(78)) |
|||
# Invalid prefix byte |
|||
raw = Base58.decode_check(MXPRV) |
|||
with pytest.raises(ValueError): |
|||
bip32._from_extended_key(raw[:45] + b'\1' + raw[46:]) |
|||
|
|||
|
|||
class TestPubKey(object): |
|||
|
|||
def test_constructor(self): |
|||
cls = bip32.PubKey |
|||
raw_pubkey = b'\2' * 33 |
|||
chain_code = bytes(32) |
|||
|
|||
# Invalid constructions |
|||
with pytest.raises(TypeError): |
|||
cls(' ' * 33, chain_code, 0, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(bytes(32), chain_code, -1, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(bytes(33), chain_code, -1, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(chain_code, chain_code, 0, 0) |
|||
with pytest.raises(TypeError): |
|||
cls(raw_pubkey, '0' * 32, 0, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(raw_pubkey, bytes(31), 0, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(raw_pubkey, chain_code, -1, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(raw_pubkey, chain_code, 1 << 32, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(raw_pubkey, chain_code, 0, -1) |
|||
with pytest.raises(ValueError): |
|||
cls(raw_pubkey, chain_code, 0, 256) |
|||
with pytest.raises(ValueError): |
|||
cls(b'\0' + b'\2' * 32, chain_code, 0, 0) |
|||
|
|||
# These are OK |
|||
cls(b'\2' + b'\2' * 32, chain_code, 0, 0) |
|||
cls(b'\3' + b'\2' * 32, chain_code, 0, 0) |
|||
cls(raw_pubkey, chain_code, (1 << 32) - 1, 0) |
|||
cls(raw_pubkey, chain_code, 0, 255) |
|||
cls(raw_pubkey, chain_code, 0, 255, mpubkey) |
|||
|
|||
# Construction from verifying key |
|||
dup = cls(mpubkey.verifying_key, chain_code, 0, 0) |
|||
assert mpubkey.ec_point() == dup.ec_point() |
|||
|
|||
# Construction from raw pubkey bytes |
|||
pubkey = mpubkey.pubkey_bytes |
|||
dup = cls(pubkey, chain_code, 0, 0) |
|||
assert mpubkey.ec_point() == dup.ec_point() |
|||
|
|||
# Construction from PubKey |
|||
with pytest.raises(TypeError): |
|||
cls(mpubkey, chain_code, 0, 0) |
|||
|
|||
def test_from_extended_key_string(self): |
|||
assert mpubcoin == Bitcoin |
|||
assert mpubkey.n == 0 |
|||
assert mpubkey.depth == 0 |
|||
assert mpubkey.parent is None |
|||
assert mpubkey.chain_code == b'>V\x83\x92`\r\x17\xb3"\xa6\x7f\xaf\xc0\x930\xf7\x1e\xdc\x12i\x9c\xe4\xc0,a\x1a\x04\xec\x16\x19\xaeK' |
|||
assert mpubkey.ec_point().x() == 44977109961578369385937116592536468905742111247230478021459394832226142714624 |
|||
|
|||
def test_extended_key(self): |
|||
# Test argument validation |
|||
with pytest.raises(TypeError): |
|||
mpubkey._extended_key('foot', bytes(33)) |
|||
with pytest.raises(ValueError): |
|||
mpubkey._extended_key(b'foo', bytes(33)) |
|||
with pytest.raises(TypeError): |
|||
mpubkey._extended_key(bytes(4), ' ' * 33) |
|||
with pytest.raises(ValueError): |
|||
mpubkey._extended_key(b'foot', bytes(32)) |
|||
mpubkey._extended_key(b'foot', bytes(33)) |
|||
|
|||
def test_extended_key_string(self): |
|||
# Implictly tests extended_key() |
|||
assert mpubkey.extended_key_string(Bitcoin) == MXPUB |
|||
chg_master = mpubkey.child(1) |
|||
chg5 = chg_master.child(5) |
|||
assert chg5.address(Bitcoin) == '1BsEFqGtcZnVBbPeimcfAFTitQdTLvUXeX' |
|||
assert chg5.extended_key_string(Bitcoin) == 'xpub6AzPNZ1SAS7zmSnj6gakQ6tAKPzRVdQzieL3eCnoeT3A89nJaJKuUYWoZuYp8xWhCs1gF9yXAwGg7zKYhvCfhk9jrb1bULhLkQCwtB1Nnn1' |
|||
|
|||
ext_key_base58 = chg5.extended_key_string(Bitcoin) |
|||
assert ext_key_base58 == 'xpub6AzPNZ1SAS7zmSnj6gakQ6tAKPzRVdQzieL3eCnoeT3A89nJaJKuUYWoZuYp8xWhCs1gF9yXAwGg7zKYhvCfhk9jrb1bULhLkQCwtB1Nnn1' |
|||
|
|||
# Check can recreate |
|||
dup, coin = bip32.from_extended_key_string(ext_key_base58) |
|||
assert coin is Bitcoin |
|||
assert dup.chain_code == chg5.chain_code |
|||
assert dup.n == chg5.n == 5 |
|||
assert dup.depth == chg5.depth == 2 |
|||
assert dup.ec_point() == chg5.ec_point() |
|||
|
|||
def test_child(self): |
|||
'''Test child derivations agree with Electrum.''' |
|||
rec_master = mpubkey.child(0) |
|||
assert rec_master.address(Bitcoin) == '18zW4D1Vxx9jVPGzsFzgXj8KrSLHt7w2cg' |
|||
chg_master = mpubkey.child(1) |
|||
assert chg_master.parent is mpubkey |
|||
assert chg_master.address(Bitcoin) == '1G8YpbkZd7bySHjpdQK3kMcHhc6BvHr5xy' |
|||
rec0 = rec_master.child(0) |
|||
assert rec0.address(Bitcoin) == '13nASW7rdE2dnSycrAP9VePhRmaLg9ziaw' |
|||
rec19 = rec_master.child(19) |
|||
assert rec19.address(Bitcoin) == '15QrXnPQ8aS8yCpA5tJkyvXfXpw8F8k3fB' |
|||
chg0 = chg_master.child(0) |
|||
assert chg0.parent is chg_master |
|||
assert chg0.address(Bitcoin) == '1L6fNSVhWjuMKNDigA99CweGEWtcqqhzDj' |
|||
|
|||
with pytest.raises(ValueError): |
|||
mpubkey.child(-1) |
|||
with pytest.raises(ValueError): |
|||
mpubkey.child(1 << 31) |
|||
# OK |
|||
mpubkey.child((1 << 31) - 1) |
|||
|
|||
def test_address(self): |
|||
assert mpubkey.address(Bitcoin) == '1ENCpq6mbb1KYcaodGG7eTpSpYvPnDjFmU' |
|||
|
|||
def test_identifier(self): |
|||
assert mpubkey.identifier() == b'\x92\x9c=\xb8\xd6\xe7\xebR\x90Td\x85\x1c\xa7\x0c\x8aE`\x87\xdd' |
|||
|
|||
def test_fingerprint(self): |
|||
assert mpubkey.fingerprint() == b'\x92\x9c=\xb8' |
|||
|
|||
def test_parent_fingerprint(self): |
|||
assert mpubkey.parent_fingerprint() == bytes(4) |
|||
child = mpubkey.child(0) |
|||
assert child.parent_fingerprint() == mpubkey.fingerprint() |
|||
|
|||
def test_pubkey_bytes(self): |
|||
# Also tests _exponent_to_bytes |
|||
pubkey = mpubkey.pubkey_bytes |
|||
assert pubkey == b'\x02cp$a\x18\xa7\xc2\x18\xfdUt\x96\xeb\xb2\xb0\x86-Y\xc6Hn\x88\xf8>\x07\xfd\x12\xce\x8a\x88\xfb\x00' |
|||
|
|||
|
|||
class TestPrivKey(object): |
|||
|
|||
def test_constructor(self): |
|||
# Includes full tests of _signing_key_from_privkey and |
|||
# _privkey_secret_exponent |
|||
cls = bip32.PrivKey |
|||
chain_code = bytes(32) |
|||
|
|||
# These are invalid |
|||
with pytest.raises(TypeError): |
|||
cls('0' * 32, chain_code, 0, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(b'0' * 31, chain_code, 0, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(MPRIVKEY, chain_code, -1, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(MPRIVKEY, chain_code, 1 << 32, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(MPRIVKEY, chain_code, 0, -1) |
|||
with pytest.raises(ValueError): |
|||
cls(MPRIVKEY, chain_code, 0, 256) |
|||
# Invalid exponents |
|||
with pytest.raises(ValueError): |
|||
cls(bip32._exponent_to_bytes(0), chain_code, 0, 0) |
|||
with pytest.raises(ValueError): |
|||
cls(bip32._exponent_to_bytes(cls.CURVE.order), chain_code, 0, 0) |
|||
|
|||
# These are good |
|||
cls(MPRIVKEY, chain_code, 0, 0) |
|||
cls(MPRIVKEY, chain_code, (1 << 32) - 1, 0) |
|||
cls(MPRIVKEY, chain_code, 0, 0) |
|||
cls(bip32._exponent_to_bytes(cls.CURVE.order - 1), chain_code, 0, 0) |
|||
privkey = cls(MPRIVKEY, chain_code, 0, 255) |
|||
|
|||
# Construction with bad parent |
|||
with pytest.raises(TypeError): |
|||
cls(MPRIVKEY, chain_code, 0, 0, privkey.public_key) |
|||
|
|||
# Construction from signing key |
|||
dup = cls(privkey.signing_key, chain_code, 0, 0) |
|||
assert dup.ec_point() == privkey.ec_point() |
|||
|
|||
# Construction from PrivKey |
|||
with pytest.raises(TypeError): |
|||
cls(privkey, chain_code, 0, 0) |
|||
|
|||
def test_secret_exponent(self): |
|||
assert mprivkey.secret_exponent() == 27118888947022743980605817563635166434451957861641813930891160184742578898176 |
|||
|
|||
def test_identifier(self): |
|||
assert mprivkey.identifier() == mpubkey.identifier() |
|||
|
|||
def test_address(self): |
|||
assert mprivkey.address(Bitcoin) == mpubkey.address(Bitcoin) |
|||
|
|||
def test_fingerprint(self): |
|||
assert mprivkey.fingerprint() == mpubkey.fingerprint() |
|||
|
|||
def test_parent_fingerprint(self): |
|||
assert mprivkey.parent_fingerprint() == bytes(4) |
|||
child = mprivkey.child(0) |
|||
assert child.parent_fingerprint() == mprivkey.fingerprint() |
|||
|
|||
def test_from_extended_key_string(self): |
|||
# Also tests privkey_bytes and public_key |
|||
assert mprivcoin is Bitcoin |
|||
assert mprivkey.privkey_bytes == MPRIVKEY |
|||
assert mprivkey.ec_point() == mpubkey.ec_point() |
|||
assert mprivkey.public_key.chain_code == mpubkey.chain_code |
|||
assert mprivkey.public_key.n == mpubkey.n |
|||
assert mprivkey.public_key.depth == mpubkey.depth |
|||
|
|||
def test_extended_key(self): |
|||
# Test argument validation |
|||
with pytest.raises(TypeError): |
|||
mprivkey._extended_key('foot', bytes(33)) |
|||
with pytest.raises(ValueError): |
|||
mprivkey._extended_key(b'foo', bytes(33)) |
|||
with pytest.raises(TypeError): |
|||
mprivkey._extended_key(bytes(4), ' ' * 33) |
|||
with pytest.raises(ValueError): |
|||
mprivkey._extended_key(b'foot', bytes(32)) |
|||
mprivkey._extended_key(b'foot', bytes(33)) |
|||
|
|||
def test_extended_key_string(self): |
|||
# Also tests extended_key, WIF and privkey_bytes |
|||
assert mprivkey.extended_key_string(Bitcoin) == MXPRV |
|||
chg_master = mprivkey.child(1) |
|||
chg5 = chg_master.child(5) |
|||
assert chg5.WIF(Bitcoin) == 'L5kTYMuajTGWdYiMoD4V8k6LS4Bg3HFMA5UGTfxG9Wh7UKu9CHFC' |
|||
ext_key_base58 = chg5.extended_key_string(Bitcoin) |
|||
assert ext_key_base58 == 'xprv9x12y3UYL4ZhYxiFzf3k2xwRmN9w6Ah9MRQSqpPC67WBFMTA2m1evkCKidz7UYBa5i8QwxmU9Ju7giqEmcPRXKXwzgAJwssNeZNQLPT3LAY' |
|||
|
|||
# Check can recreate |
|||
dup, coin = bip32.from_extended_key_string(ext_key_base58) |
|||
assert coin is Bitcoin |
|||
assert dup.chain_code == chg5.chain_code |
|||
assert dup.n == chg5.n == 5 |
|||
assert dup.depth == chg5.depth == 2 |
|||
assert dup.ec_point() == chg5.ec_point() |
|||
|
|||
def test_child(self): |
|||
'''Test child derivations agree with Electrum.''' |
|||
# Also tests WIF, address |
|||
rec_master = mprivkey.child(0) |
|||
assert rec_master.address(Bitcoin) == '18zW4D1Vxx9jVPGzsFzgXj8KrSLHt7w2cg' |
|||
chg_master = mprivkey.child(1) |
|||
assert chg_master.parent is mprivkey |
|||
assert chg_master.address(Bitcoin) == '1G8YpbkZd7bySHjpdQK3kMcHhc6BvHr5xy' |
|||
rec0 = rec_master.child(0) |
|||
assert rec0.WIF(Bitcoin) == 'L2M6WWMdu3YfWxvLGF76HZgHCA6idwVQx5QL91vfdqeZi8XAgWkz' |
|||
rec19 = rec_master.child(19) |
|||
assert rec19.WIF(Bitcoin) == 'KwMHa1fynU2J2iBGCuBZxumM2qDXHe5tVPU9VecNGQv3UCqnET7X' |
|||
chg0 = chg_master.child(0) |
|||
assert chg0.parent is chg_master |
|||
assert chg0.WIF(Bitcoin) == 'L4J1esD4rYuBHXwjg72yi7Rw4G3iF2yUHt7LN9trpC3snCppUbq8' |
|||
|
|||
with pytest.raises(ValueError): |
|||
mprivkey.child(-1) |
|||
with pytest.raises(ValueError): |
|||
mprivkey.child(1 << 32) |
|||
# OK |
|||
mprivkey.child((1 << 32) - 1) |
|||
|
|||
|
|||
class TestVectors(): |
|||
|
|||
def test_vector1(self): |
|||
seed = bytes.fromhex("000102030405060708090a0b0c0d0e0f") |
|||
|
|||
# Chain m |
|||
m = bip32.PrivKey.from_seed(seed) |
|||
xprv = m.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" |
|||
xpub = m.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" |
|||
|
|||
# Chain m/0H |
|||
m1 = m.child(0 + m.HARDENED) |
|||
xprv = m1.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" |
|||
xpub = m1.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw" |
|||
|
|||
# Chain m/0H/1 |
|||
m2 = m1.child(1) |
|||
xprv = m2.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" |
|||
xpub = m2.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ" |
|||
|
|||
# Chain m/0H/1/2H |
|||
m3 = m2.child(2 + m.HARDENED) |
|||
xprv = m3.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" |
|||
xpub = m3.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5" |
|||
|
|||
# Chain m/0H/1/2H/2 |
|||
m4 = m3.child(2) |
|||
xprv = m4.extended_key_string(Bitcoin) |
|||
assert xprv == "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" |
|||
xpub = m4.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV" |
|||
|
|||
# Chain m/0H/1/2H/2/1000000000 |
|||
m5 = m4.child(1000000000) |
|||
xprv = m5.extended_key_string(Bitcoin) |
|||
assert xprv == "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" |
|||
xpub = m5.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" |
|||
|
|||
def test_vector2(self): |
|||
seed = bytes.fromhex("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542") |
|||
# Chain m |
|||
m = bip32.PrivKey.from_seed(seed) |
|||
xprv = m.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" |
|||
xpub = m.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" |
|||
|
|||
# Chain m/0 |
|||
m1 = m.child(0) |
|||
xprv = m1.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" |
|||
xpub = m1.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH" |
|||
|
|||
# Chain m/0H/2147483647H |
|||
m2 = m1.child(2147483647 + m.HARDENED) |
|||
xprv = m2.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" |
|||
xpub = m2.public_key.extended_key_string(Bitcoin) |
|||
assert xpub == "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a" |
|||
|
|||
# Chain m/0H/2147483647H/1 |
|||
m3 = m2.child(1) |
|||
xprv = m3.extended_key_string(Bitcoin) |
|||
xpub = m3.public_key.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" |
|||
assert xpub == "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon" |
|||
|
|||
# Chain m/0/2147483647H/1/2147483646H |
|||
m4 = m3.child(2147483646 + m.HARDENED) |
|||
xprv = m4.extended_key_string(Bitcoin) |
|||
xpub = m4.public_key.extended_key_string(Bitcoin) |
|||
assert xprv == "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" |
|||
assert xpub == "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" |
|||
|
|||
# Chain m/0/2147483647H/1/2147483646H/2 |
|||
m5 = m4.child(2) |
|||
xprv = m5.extended_key_string(Bitcoin) |
|||
xpub = m5.public_key.extended_key_string(Bitcoin) |
|||
assert xprv == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" |
|||
assert xpub == "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt" |
|||
|
|||
def test_vector3(self): |
|||
seed = bytes.fromhex("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be") |
|||
|
|||
# Chain m |
|||
m = bip32.PrivKey.from_seed(seed) |
|||
xprv = m.extended_key_string(Bitcoin) |
|||
xpub = m.public_key.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6" |
|||
assert xpub == "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13" |
|||
|
|||
# Chain m/0H |
|||
m1 = m.child(0 + m.HARDENED) |
|||
xprv = m1.extended_key_string(Bitcoin) |
|||
xpub = m1.public_key.extended_key_string(Bitcoin) |
|||
assert xprv == "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L" |
|||
assert xpub == "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y" |
@ -0,0 +1,306 @@ |
|||
# Copyright (c) 2017, Neil Booth |
|||
# |
|||
# All rights reserved. |
|||
# |
|||
# See the file "LICENCE" for information about the copyright |
|||
# and warranty status of this software. |
|||
|
|||
'''Logic for BIP32 Hierarchical Key Derviation.''' |
|||
|
|||
import struct |
|||
|
|||
import ecdsa |
|||
import ecdsa.ellipticcurve as EC |
|||
import ecdsa.numbertheory as NT |
|||
|
|||
from lib.coins import Coin |
|||
from lib.hash import Base58, hmac_sha512, hash160 |
|||
from lib.util import cachedproperty, bytes_to_int, int_to_bytes |
|||
|
|||
|
|||
class DerivationError(Exception): |
|||
'''Raised when an invalid derivation occurs.''' |
|||
|
|||
|
|||
class _KeyBase(object): |
|||
'''A BIP32 Key, public or private.''' |
|||
|
|||
CURVE = ecdsa.SECP256k1 |
|||
|
|||
def __init__(self, chain_code, n, depth, parent): |
|||
if not isinstance(chain_code, (bytes, bytearray)): |
|||
raise TypeError('chain code must be raw bytes') |
|||
if len(chain_code) != 32: |
|||
raise ValueError('invalid chain code') |
|||
if not 0 <= n < 1 << 32: |
|||
raise ValueError('invalid child number') |
|||
if not 0 <= depth < 256: |
|||
raise ValueError('invalid depth') |
|||
if parent is not None: |
|||
if not isinstance(parent, type(self)): |
|||
raise TypeError('parent key has bad type') |
|||
self.chain_code = chain_code |
|||
self.n = n |
|||
self.depth = depth |
|||
self.parent = parent |
|||
|
|||
def _hmac_sha512(self, msg): |
|||
'''Use SHA-512 to provide an HMAC, returned as a pair of 32-byte |
|||
objects. |
|||
''' |
|||
hmac = hmac_sha512(self.chain_code, msg) |
|||
return hmac[:32], hmac[32:] |
|||
|
|||
def _extended_key(self, ver_bytes, raw_serkey): |
|||
'''Return the 78-byte extended key given prefix version bytes and |
|||
serialized key bytes. |
|||
''' |
|||
if not isinstance(ver_bytes, (bytes, bytearray)): |
|||
raise TypeError('ver_bytes must be raw bytes') |
|||
if len(ver_bytes) != 4: |
|||
raise ValueError('ver_bytes must have length 4') |
|||
if not isinstance(raw_serkey, (bytes, bytearray)): |
|||
raise TypeError('raw_serkey must be raw bytes') |
|||
if len(raw_serkey) != 33: |
|||
raise ValueError('raw_serkey must have length 33') |
|||
|
|||
return (ver_bytes + bytes([self.depth]) |
|||
+ self.parent_fingerprint() + struct.pack('>I', self.n) |
|||
+ self.chain_code + raw_serkey) |
|||
|
|||
def fingerprint(self): |
|||
'''Return the key's fingerprint as 4 bytes.''' |
|||
return self.identifier()[:4] |
|||
|
|||
def parent_fingerprint(self): |
|||
'''Return the parent key's fingerprint as 4 bytes.''' |
|||
return self.parent.fingerprint() if self.parent else bytes(4) |
|||
|
|||
def extended_key_string(self, coin): |
|||
'''Return an extended key as a base58 string.''' |
|||
return Base58.encode_check(self.extended_key(coin)) |
|||
|
|||
|
|||
class PubKey(_KeyBase): |
|||
'''A BIP32 public key.''' |
|||
|
|||
def __init__(self, pubkey, chain_code, n, depth, parent=None): |
|||
super().__init__(chain_code, n, depth, parent) |
|||
if isinstance(pubkey, ecdsa.VerifyingKey): |
|||
self.verifying_key = pubkey |
|||
else: |
|||
self.verifying_key = self._verifying_key_from_pubkey(pubkey) |
|||
self.addresses = {} |
|||
|
|||
@classmethod |
|||
def _verifying_key_from_pubkey(cls, pubkey): |
|||
'''Converts a 33-byte compressed pubkey into an ecdsa.VerifyingKey |
|||
object''' |
|||
if not isinstance(pubkey, (bytes, bytearray)): |
|||
raise TypeError('pubkey must be raw bytes') |
|||
if len(pubkey) != 33: |
|||
raise ValueError('pubkey must be 33 bytes') |
|||
if pubkey[0] not in (2, 3): |
|||
raise ValueError('invalid pubkey prefix byte') |
|||
curve = cls.CURVE.curve |
|||
|
|||
is_odd = pubkey[0] == 3 |
|||
x = bytes_to_int(pubkey[1:]) |
|||
|
|||
# p is the finite field order |
|||
a, b, p = curve.a(), curve.b(), curve.p() |
|||
y2 = pow(x, 3, p) + b |
|||
assert a == 0 # Otherwise y2 += a * pow(x, 2, p) |
|||
y = NT.square_root_mod_prime(y2 % p, p) |
|||
if bool(y & 1) != is_odd: |
|||
y = p - y |
|||
point = EC.Point(curve, x, y) |
|||
|
|||
return ecdsa.VerifyingKey.from_public_point(point, curve=cls.CURVE) |
|||
|
|||
@cachedproperty |
|||
def pubkey_bytes(self): |
|||
'''Return the compressed public key as 33 bytes.''' |
|||
point = self.verifying_key.pubkey.point |
|||
prefix = bytes([2 + (point.y() & 1)]) |
|||
padded_bytes = _exponent_to_bytes(point.x()) |
|||
return prefix + padded_bytes |
|||
|
|||
def address(self, coin): |
|||
"The public key as a P2PKH address" |
|||
address = self.addresses.get(coin) |
|||
if not address: |
|||
address = coin.P2PKH_address_from_pubkey(self.pubkey_bytes) |
|||
self.addresses[coin] = address |
|||
return address |
|||
|
|||
def ec_point(self): |
|||
return self.verifying_key.pubkey.point |
|||
|
|||
def child(self, n): |
|||
'''Return the derived child extended pubkey at index N.''' |
|||
if not 0 <= n < (1 << 31): |
|||
raise ValueError('invalid BIP32 public key child number') |
|||
|
|||
msg = self.pubkey_bytes + struct.pack('>I', n) |
|||
L, R = self._hmac_sha512(msg) |
|||
|
|||
curve = self.CURVE |
|||
L = bytes_to_int(L) |
|||
if L >= curve.order: |
|||
raise DerivationError |
|||
|
|||
point = curve.generator * L + self.ec_point() |
|||
if point == EC.INFINITY: |
|||
raise DerivationError |
|||
|
|||
verkey = ecdsa.VerifyingKey.from_public_point(point, curve=curve) |
|||
|
|||
return PubKey(verkey, R, n, self.depth + 1, self) |
|||
|
|||
def identifier(self): |
|||
'''Return the key's identifier as 20 bytes.''' |
|||
return hash160(self.pubkey_bytes) |
|||
|
|||
def extended_key(self, coin): |
|||
'''Return a raw extended public key.''' |
|||
return self._extended_key(coin.XPUB_VERBYTES, self.pubkey_bytes) |
|||
|
|||
|
|||
class PrivKey(_KeyBase): |
|||
'''A BIP32 private key.''' |
|||
|
|||
HARDENED = 1 << 31 |
|||
|
|||
def __init__(self, privkey, chain_code, n, depth, parent=None): |
|||
super().__init__(chain_code, n, depth, parent) |
|||
if isinstance(privkey, ecdsa.SigningKey): |
|||
self.signing_key = privkey |
|||
else: |
|||
self.signing_key = self._signing_key_from_privkey(privkey) |
|||
|
|||
@classmethod |
|||
def _signing_key_from_privkey(cls, privkey): |
|||
'''Converts a 32-byte privkey into an ecdsa.SigningKey object.''' |
|||
exponent = cls._privkey_secret_exponent(privkey) |
|||
return ecdsa.SigningKey.from_secret_exponent(exponent, curve=cls.CURVE) |
|||
|
|||
@classmethod |
|||
def _privkey_secret_exponent(cls, privkey): |
|||
'''Return the private key as a secret exponent if it is a valid private |
|||
key.''' |
|||
if not isinstance(privkey, (bytes, bytearray)): |
|||
raise TypeError('privkey must be raw bytes') |
|||
if len(privkey) != 32: |
|||
raise ValueError('privkey must be 32 bytes') |
|||
exponent = bytes_to_int(privkey) |
|||
if not 1 <= exponent < cls.CURVE.order: |
|||
raise ValueError('privkey represents an invalid exponent') |
|||
|
|||
return exponent |
|||
|
|||
@classmethod |
|||
def from_seed(cls, seed): |
|||
# This hard-coded message string seems to be coin-independent... |
|||
hmac = hmac_sha512(b'Bitcoin seed', seed) |
|||
privkey, chain_code = hmac[:32], hmac[32:] |
|||
return cls(privkey, chain_code, 0, 0) |
|||
|
|||
@cachedproperty |
|||
def privkey_bytes(self): |
|||
'''Return the serialized private key (no leading zero byte).''' |
|||
return _exponent_to_bytes(self.secret_exponent()) |
|||
|
|||
@cachedproperty |
|||
def public_key(self): |
|||
'''Return the corresponding extended public key.''' |
|||
verifying_key = self.signing_key.get_verifying_key() |
|||
parent_pubkey = self.parent.public_key if self.parent else None |
|||
return PubKey(verifying_key, self.chain_code, self.n, self.depth, |
|||
parent_pubkey) |
|||
|
|||
def ec_point(self): |
|||
return self.public_key.ec_point() |
|||
|
|||
def secret_exponent(self): |
|||
'''Return the private key as a secret exponent.''' |
|||
return self.signing_key.privkey.secret_multiplier |
|||
|
|||
def WIF(self, coin): |
|||
'''Return the private key encoded in Wallet Import Format.''' |
|||
return coin.privkey_WIF(self.privkey_bytes, compressed=True) |
|||
|
|||
def address(self, coin): |
|||
"The public key as a P2PKH address" |
|||
return self.public_key.address(coin) |
|||
|
|||
def child(self, n): |
|||
'''Return the derived child extended privkey at index N.''' |
|||
if not 0 <= n < (1 << 32): |
|||
raise ValueError('invalid BIP32 private key child number') |
|||
|
|||
if n >= self.HARDENED: |
|||
serkey = b'\0' + self.privkey_bytes |
|||
else: |
|||
serkey = self.public_key.pubkey_bytes |
|||
|
|||
msg = serkey + struct.pack('>I', n) |
|||
L, R = self._hmac_sha512(msg) |
|||
|
|||
curve = self.CURVE |
|||
L = bytes_to_int(L) |
|||
exponent = (L + bytes_to_int(self.privkey_bytes)) % curve.order |
|||
if exponent == 0 or L >= curve.order: |
|||
raise DerivationError |
|||
|
|||
privkey = _exponent_to_bytes(exponent) |
|||
|
|||
return PrivKey(privkey, R, n, self.depth + 1, self) |
|||
|
|||
def identifier(self): |
|||
'''Return the key's identifier as 20 bytes.''' |
|||
return self.public_key.identifier() |
|||
|
|||
def extended_key(self, coin): |
|||
'''Return a raw extended private key.''' |
|||
return self._extended_key(coin.XPRV_VERBYTES, |
|||
b'\0' + self.privkey_bytes) |
|||
|
|||
|
|||
def _exponent_to_bytes(exponent): |
|||
'''Convert an exponent to 32 big-endian bytes''' |
|||
return (bytes(32) + int_to_bytes(exponent))[-32:] |
|||
|
|||
def _from_extended_key(ekey): |
|||
'''Return a PubKey or PrivKey from an extended key raw bytes.''' |
|||
if not isinstance(ekey, (bytes, bytearray)): |
|||
raise TypeError('extended key must be raw bytes') |
|||
if len(ekey) != 78: |
|||
raise ValueError('extended key must have length 78') |
|||
|
|||
is_public, coin = Coin.lookup_xverbytes(ekey[:4]) |
|||
depth = ekey[4] |
|||
fingerprint = ekey[5:9] # Not used |
|||
n, = struct.unpack('>I', ekey[9:13]) |
|||
chain_code = ekey[13:45] |
|||
|
|||
if is_public: |
|||
pubkey = ekey[45:] |
|||
key = PubKey(pubkey, chain_code, n, depth) |
|||
else: |
|||
if ekey[45] is not 0: |
|||
raise ValueError('invalid extended private key prefix byte') |
|||
privkey = ekey[46:] |
|||
key = PrivKey(privkey, chain_code, n, depth) |
|||
|
|||
return key, coin |
|||
|
|||
def from_extended_key_string(ekey_str): |
|||
'''Given an extended key string, such as |
|||
|
|||
xpub6BsnM1W2Y7qLMiuhi7f7dbAwQZ5Cz5gYJCRzTNainXzQXYjFwtuQXHd |
|||
3qfi3t3KJtHxshXezfjft93w4UE7BGMtKwhqEHae3ZA7d823DVrL |
|||
|
|||
return a (key, coin) pair. key is either a PubKey or PrivKey. |
|||
''' |
|||
return _from_extended_key(Base58.decode_check(ekey_str)) |
Loading…
Reference in new issue