The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── LICENCE
├── MANIFEST.in
├── README.md
├── common.spec
├── config.ini_sample
├── contrib
    ├── electrumpersonalserver.confd
    ├── electrumpersonalserver.initd
    └── electrumpersonalserver.service
├── docs
    ├── developer-notes.md
    ├── pubkeys
    │   └── belcher.asc
    └── signed-donation-addresses.txt
├── electrum-personal-server-rescan.bat
├── electrumpersonalserver
    ├── __init__.py
    ├── bitcoin
    │   ├── __init__.py
    │   ├── deterministic.py
    │   ├── main.py
    │   ├── py2specials.py
    │   ├── py3specials.py
    │   ├── secp256k1_deterministic.py
    │   ├── secp256k1_main.py
    │   ├── secp256k1_transaction.py
    │   └── transaction.py
    ├── certs
    │   ├── cert.crt
    │   └── cert.key
    └── server
    │   ├── __init__.py
    │   ├── common.py
    │   ├── deterministicwallet.py
    │   ├── electrumprotocol.py
    │   ├── hashes.py
    │   ├── jsonrpc.py
    │   ├── mempoolhistogram.py
    │   ├── merkleproof.py
    │   ├── peertopeer.py
    │   ├── socks.py
    │   └── transactionmonitor.py
├── release-notes
├── setup.cfg
├── setup.py
└── test
    ├── test_electrum_protocol.py
    ├── test_merkleproof.py
    ├── test_parse_mpks.py
    └── test_transactionmonitor.py


/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.swp
3 | config.ini*
4 | .cache/
5 | .pytest_cache/
6 | 


--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Permission is hereby granted, free of charge, to any person obtaining
 4 | a copy of this software and associated documentation files (the
 5 | "Software"), to deal in the Software without restriction, including
 6 | without limitation the rights to use, copy, modify, merge, publish,
 7 | distribute, sublicense, and/or sell copies of the Software, and to
 8 | permit persons to whom the Software is furnished to do so, subject to
 9 | the following conditions:
10 | 
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 | 
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | 


--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include doc/*.md
3 | include electrumpersonalserver/certs/*
4 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # Electrum Personal Server
  2 | 
  3 | Electrum Personal Server aims to make using Electrum bitcoin wallet more secure
  4 | and more private. It makes it easy to connect your Electrum wallet to your own
  5 | full node.
  6 | 
  7 | [Full node](https://en.bitcoin.it/wiki/Full_node) wallets are important in
  8 | bitcoin because they are a big part of what makes the system trustless. No
  9 | longer do people have to trust a financial institution like a bank or Paypal,
 10 | they can run software on their own computers. If bitcoin is digital gold, then
 11 | a full node wallet is your own personal goldsmith who checks for you that
 12 | received payments are genuine.
 13 | 
 14 | Full node wallets are also important for privacy. Using Electrum under default
 15 | configuration requires it to send (hashes of) all your bitcoin addresses to some
 16 | server. That server can then easily spy on your transactions. Full node
 17 | wallets like Electrum Personal Server would download the entire blockchain and
 18 | scan it for the user's own addresses, and therefore don't reveal to anyone else
 19 | which bitcoin addresses they are interested in.
 20 | 
 21 | ## Contents
 22 | 
 23 | - [Features](#features)
 24 | - [Detailed how-to guide](#how-to)
 25 | - [Quick start for Debian/Ubuntu](#quick-start-on-a-debianubuntu-machine-with-a-running-bitcoin-full-node)
 26 | - [Links to other setup guides](#links-to-other-setup-guides)
 27 | - [How to expose the server to the internet](#exposure-to-the-internet)
 28 | - [How is this different from other Electrum servers ?](#how-is-this-different-from-other-electrum-servers-)
 29 | - [Articles, Discussion and Talks](#articles-discussion-and-talks)
 30 | - [Contributing](#contributing)
 31 | 
 32 | ### Features
 33 | 
 34 | - Fully-featured Electrum server for a single user. Combine full node security
 35 |   and privacy with all of Electrum's feature-richness: (Hardware wallet
 36 |   integration, [Multisignature wallets](http://docs.electrum.org/en/latest/multisig.html),
 37 |   [Offline signing](http://docs.electrum.org/en/latest/coldstorage.html),
 38 |   [Seed recovery phrases](https://en.bitcoin.it/wiki/Seed_phrase), Coin control,
 39 |   Fee-bumping)
 40 | - Maximally lightweight. Very low CPU, RAM and disk space requirements. Only a
 41 |   full node required.
 42 | - Compatible with all Bitcoin Core resource-saving features:
 43 |   - [Pruning](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.12.0.md#wallet-pruning)
 44 |   - [Blocksonly](https://bitcointalk.org/index.php?topic=1377345.0)
 45 |   - Disabled txindex
 46 | - Scriptable transaction broadcasting. When the user click "Send" the server
 47 |   can be configured to run a system call with the new transaction:
 48 |   - Broadcast transactions through Tor, for [resisting traffic analysis](https://en.bitcoin.it/wiki/Privacy#Tor_and_tor_broadcasting).
 49 |   - By writing a shell script (eg. `send-tx-over-sms.sh`) the server can
 50 |     broadcast transactions via SMS, radio or any other creative way.
 51 | 
 52 | ## How To
 53 | 
 54 | - If you dont already have them, download and install Bitcoin Core version 0.17
 55 |   or higher. Make sure you
 56 |   [verify the digital signatures](https://bitcoin.stackexchange.com/questions/50185/how-to-verify-bitcoin-core-release-signing-keys)
 57 |   of any binaries before running them, or compile from source. The Bitcoin node
 58 |   must have wallet functionality enabled, and must have the RPC server switched on (`server=1`
 59 |   in bitcoin.conf). Create a wallet dedicated to Electrum Personal Server by running
 60 |   `bitcoin-cli createwallet electrumpersonalserver true true "" false false true`
 61 |   for Bitcoin Core v23.0 and up, or
 62 |   `wallet=electrumpersonalserver` to the bitcoin.conf file for previous versions.
 63 | 
 64 | - If you dont already have it, download and install
 65 |   [Electrum bitcoin wallet](https://electrum.org/), and set up your Electrum
 66 |   wallet (for example by linking your hardware wallet). To avoid damaging
 67 |   privacy by connecting to public Electrum servers, disconnect from the
 68 |   internet first or run Electrum with the command line argument
 69 |   `--server localhost:50002:s`. To avoid accidentally connecting to public
 70 |   electrum servers, also use the command line argument `--offline`.
 71 | 
 72 | - Download the [latest release](https://github.com/chris-belcher/electrum-personal-server/releases)
 73 |   of Electrum Personal Server. If using Windows OS take the packaged binary
 74 |   release build `electrumpersonalserver-windows-release-XXX.zip`.
 75 | 
 76 | - Extract and enter the directory, and copy the file `config.ini_sample` to
 77 |   `config.ini`. Edit the file `config.ini` to configure everything about the
 78 |   server. Add your wallet master public keys or watch-only addresses to the
 79 |   `[master-public-keys]` and `[watch-only-addresses]` sections. Master public
 80 |   keys for an Electrum wallet (which start with xpub/ypub/zpub/etc) can be found
 81 |   in the Electrum client menu `Wallet` -> `Information`. You can add multiple
 82 |   master public keys or watch-only addresses by adding separate lines for the
 83 |   different keys/addresses:
 84 | 
 85 |       wallet1 = xpub661MyMwAqRbcF...
 86 |       wallet2 = xpub7712KLsfsg46G...
 87 | 
 88 | - If you created a wallet dedicated to Electrum Personal Server in Bitcoin Core,
 89 |   you have to modify the line `wallet_filename` in the `[bitcoin-rpc]` section
 90 |   with the name of the wallet, for example `wallet_filename = electrumpersonalserver`.
 91 | 
 92 | - If using the windows packaged binary release, drag the file `config.ini` onto
 93 |   the file `electrum-personal-server.exe` to run the server, or on the command
 94 |   line run `electrum-personal-server config.ini`.
 95 | 
 96 | - If installing from the source release, install Electrum Personal Server in
 97 |   your home directory with `pip3 install --user .`. On Linux the script
 98 |   `electrum-personal-server` will be installed in `~/.local/bin`. Please note,
 99 |   if for some reason, you want to make a system-wide install, simply run
100 |   `pip3 install .` as root (e.g. if you have `sudo` setup, you could use:
101 |   `sudo pip3 install .`). Run `electrum-personal-server /path/to/config.ini`
102 |   to start Electrum Personal Server.
103 | 
104 | - The first time the server is run it will import all configured addresses as
105 |   watch-only into the Bitcoin node, and then exit.
106 |   If the wallets contain historical transactions you can use the rescan script
107 |   (`electrum-personal-server --rescan /path/to/config.ini`) to make them appear.
108 |   If using the windows packaged binary release build then drag the file
109 |   `config.ini` onto the file `electrum-personal-server-rescan.bat`.
110 | 
111 | - Run the server again which will start Electrum Personal Server. Wait until
112 |   the message `Listening for Electrum Wallet ...` appears and then tell
113 |   Electrum to connect to the server in `Tools` -> `Server`. By default the
114 |   server details are `localhost` if running on the same machine. Make sure the
115 |   port number matches what is written in `config.ini` (port 50002 by default).
116 | 
117 | Pro Tip: run Electrum wallet with the command line arguments `--oneserver --server localhost:50002:s`.
118 | This stops Electrum connecting to other servers to obtain block
119 | headers; and locks Electrum to connect only to your server, disabling the GUI
120 | button to stop accidental connections. This helps avoid a user accidentally
121 | ruining their privacy by connecting to public Electrum servers. Another way
122 | to do this is to open Electrum's config file and edit the lines to
123 | `oneserver=true`.
124 | 
125 | Pro Tip2: run tor on the same machine as Electrum Personal Server. Then by
126 | default transactions will be broadcast through tor. If running tor, also set
127 | `walletbroadcast=0` in your `bitcoin.conf`. This prevents the node from
128 | rebroadcasting transactions without tor.
129 | 
130 | ### Quick start on a Debian/Ubuntu machine with a running Bitcoin full node
131 | 
132 | 1. Download the [latest release](https://github.com/chris-belcher/electrum-personal-server/releases)
133 |    of Electrum Personal Server. (Not the Windows version, the "Source code" zip or
134 |    tar.gz.)
135 | 1. Extract the compressed file
136 | 1. Enter the directory
137 | 1. `cp config.ini_sample config.ini`
138 | 1. Edit the config.ini file:
139 |    1. Add bitcoind back-end RPC auth information
140 |    1. Add wallet master public keys for your wallets
141 | 1. Install the server to your home directory with `pip3 install --user .`
142 | 1. Make sure `~/.local/bin` is in your \$PATH (`echo $PATH`). If not, add it:
143 |    `echo 'PATH=$HOME/.local/bin:$PATH' >> ~/.profile`, logout, and log in again
144 | 1. Run the server: `electrum-personal-server config.ini`
145 | 1. Rescan if needed: `electrum-personal-server --rescan config.ini`
146 | 1. Restart the server if needed
147 | 1. Start your Electrum wallet: `electrum --oneserver --server localhost:50002:s`.
148 | 
149 | ### Updating to a new version
150 | 
151 | - Use `pip3 uninstall electrum-personal-server`, then use the same
152 |   `pip3 install ...` command as above to install the new version.
153 | 
154 | - You can always use `electrum-personal-server --version` to check which version you're running.
155 | 
156 | Updating to a new version of Electrum Personal Server does not require a blockchain rescan.
157 | 
158 | ### Links to other setup guides
159 | 
160 | - [How to setup Electrum Personal Server on a Raspberry Pi](https://raspibolt.github.io/raspibolt/raspibolt_64_electrum.html)
161 | - [Electrum Personal Server on Windows 10](https://driftwoodpalace.github.io/Hodl-Guide/hodl-guide_63_eps-win.html)
162 | - [Running Electrum Personal Server on Mac OS](https://driftwoodpalace.github.io/Hodl-Guide/hodl-guide_64_eps-mac.html)
163 | - [How to set up your own Bitcoin node, Electrum wallet and Server](https://curiosityoverflow.xyz/posts/bitcoin-electrum-wallet/)
164 | - [How to set up Wireguard to connect to EPS](https://curiosityoverflow.xyz/posts/wireguard-eps/)
165 | - [Linux setup video tutorial on youtube](https://www.youtube.com/watch?v=1JMP4NZCC5g)
166 | - [BTCPay Server integration with Electrum Personal Server](https://docs.btcpayserver.org/ElectrumPersonalServer/)
167 | - [Using Electrum Personal Server with a Bitseed node](https://github.com/john-light/bitcoin/blob/master/eps.md)
168 | - [Spanish language video tutorial / Instalación del servidor Electrum Personal Server](https://www.youtube.com/watch?v=F3idwecYvcU)
169 | - [Japanese language setup guide](https://freefromjp.wordpress.com/2019/07/13/electrum-personal-server-%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB/)
170 | - [Connect to Electrum Personal Server via Wireguard ](https://curiosityoverflow.xyz/posts/wireguard-eps/#connecting-to-electrum-personal-server)
171 | 
172 | #### Exposure to the Internet
173 | 
174 | Right now, Electrum Personal Server is easiest to use when it, your full node
175 | and your Electrum wallet are all on the same computer.
176 | 
177 | Other people should not be connecting to your server. They won't be
178 | able to synchronize their wallet, and they could potentially learn all your
179 | wallet transactions. By default the server will accept connections only from
180 | `localhost`, though this can be changed in the configuration file.
181 | 
182 | The whitelisting feature can be used accept only certain IP addresses ranges
183 | connecting to the server. The Electrum protocol uses SSL for encryption. If
184 | your wallet connects over the public internet you should generate your own
185 | SSL certificate instead of using the default one, otherwise your connection
186 | can be decrypted. See the configuration file for instruction on how to do
187 | this.
188 | 
189 | Another option is to use a SSH tunnel to reach Electrum Personal Server. SSH
190 | connections are encrypted and authenticated. This can be done on the command
191 | line with: `ssh username@host -L 50002:localhost:50002` or with [Putty](https://www.putty.org/)
192 | for Windows. Then connect Electrum to localhost, and SSH will forward that
193 | connection to the server.
194 | 
195 | ##### Number of connections
196 | 
197 | Right now Electrum Personal Server can only accept one connection at a time.
198 | 
199 | ##### Lightning Network
200 | 
201 | Right now Electrum Personal Server does not support Lightning Network which
202 | Electrum wallet 4.0 and above implements.
203 | 
204 | #### How is this different from other Electrum servers ?
205 | 
206 | They are different approaches with different tradeoffs. Electrum Personal
207 | Server is compatible with pruning, blocksonly and txindex=0, uses less CPU and
208 | RAM, is suitable for being used intermittently rather than needing to be
209 | always-on, and doesn't require an index of every bitcoin address ever used. The
210 | tradeoff is when recovering an old wallet, you must import your wallet first
211 | and you may need to rescan, so it loses the "instant on" feature of Electrum
212 | wallet. Other Electrum server implementations will be able to sync your wallet
213 | immediately even if you have historical transactions, and they can serve
214 | multiple Electrum connections at once.
215 | 
216 | Traditional Electrum servers inherently are not very scalable and use many
217 | resources which push people towards using centralized solutions. This is what
218 | we'd like to avoid with Electrum Personal Server.
219 | 
220 | Definitely check out other implementations:
221 | - [ElectrumX](https://github.com/spesmilo/electrumx) - Full Electrum server maintained by the Electrum project
222 | - [Electrs](https://github.com/romanz/electrs) - Full Electrum server coded in rust
223 | - [Bitcoin Wallet Tracker](https://github.com/bwt-dev/bwt) - Wallet indexer coded in rust
224 | - [Obelisk](https://github.com/parazyd/obelisk) - Minimal Electrum server using zeromq and libbitcoin as backend
225 | 
226 | #### Further ideas for work
227 | 
228 | - Allowing connections from more than one Electrum instance at a time. See issue
229 |   [#50](https://github.com/chris-belcher/electrum-personal-server/issues/50). First
230 |   the server code should be separated from the networking code.
231 | - Fix mempool lock/CPU bottleneck issue. See issue [#96](https://github.com/chris-belcher/electrum-personal-server/issues/96).
232 | - Research and develop an easier way of rescanning the wallet when blockchain
233 |   pruning is enabled. See issue [#85](https://github.com/chris-belcher/electrum-personal-server/issues/85).
234 | - Developing some way for Electrum servers to authenticate clients, so that
235 |   Electrum Personal Server can accept connections from the entire internet but
236 |   without a fear of privacy loss.
237 | - Dynamic adding of wallet master public keys. Perhaps by polling for changes
238 |   in the config file.
239 | 
240 | ## Contact
241 | 
242 | I can be contacted on freenode IRC on the `#bitcoin` and `#electrum` channels,
243 | by email or on [twitter](https://twitter.com/chris_belcher_/).
244 | 
245 | My PGP key fingerprint is: `0A8B 038F 5E10 CC27 89BF CFFF EF73 4EA6 77F3 1129`.
246 | 
247 | ## Articles, Discussion and Talks
248 | 
249 | - [BitcoinMagazine.com article](https://bitcoinmagazine.com/articles/electrum-personal-server-will-give-users-full-node-security-they-need/)
250 | - [Electrum Personal Server talk at London Bitcoin Developer Meetup](https://www.youtube.com/watch?v=uKMXYdfm-is)
251 | - Electrum Personal Server used as a building block for systems which use
252 |   bitcoin without internet access. See [here](https://twitter.com/notgrubles/status/1091011511961731073)
253 |   and [here](https://medium.com/hackernoon/completely-offline-bitcoin-transactions-4e58324637bd)
254 |   for information and setup guide.
255 | - [Mailing list email](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-February/015707.html)
256 | - [Bitcointalk thread](https://bitcointalk.org/index.php?topic=2664747.msg27179198)
257 | - [Nasdaq article](https://www.nasdaq.com/article/the-electrum-personal-server-will-give-users-the-full-node-security-they-need-cm920443)
258 | - [Bitcoinnews.ru article (russian)](https://bitcoinnews.ru/novosti/electrum-personal-server-uluchshennaya-versiya-/)
259 | - [bits.media article (russian)](https://bits.media/razrabotchiki-electrum-opublikovali-alfa-versiyu-electrum-personal-server/)
260 | 
261 | ## Contributing
262 | 
263 | Donate to help improve Electrum Personal Server: `bc1qe74qzd256kxevq2gn7gmscs564lfk5tqrxqsuy`. Signed donation addresses can be found [here](/docs/signed-donation-addresses.txt).
264 | 
265 | This is open source project which happily accepts coding contributions from
266 | anyone. See [developer-notes.md](docs/developer-notes.md).
267 | 


--------------------------------------------------------------------------------
/common.spec:
--------------------------------------------------------------------------------
 1 | # -*- mode: python -*-
 2 | #pyinstaller config file
 3 | 
 4 | block_cipher = None
 5 | 
 6 | 
 7 | a = Analysis(['electrumpersonalserver\\server\\common.py'],
 8 |              pathex=['Z:\\eps'],
 9 |              binaries=[],
10 |              datas=[],
11 |              hiddenimports=[],
12 |              hookspath=[],
13 |              runtime_hooks=[],
14 |              excludes=[],
15 |              win_no_prefer_redirects=False,
16 |              win_private_assemblies=False,
17 |              cipher=block_cipher,
18 |              noarchive=False)
19 | pyz = PYZ(a.pure, a.zipped_data,
20 |              cipher=block_cipher)
21 | exe = EXE(pyz,
22 |           a.scripts,
23 |           a.binaries,
24 |           a.zipfiles,
25 |           a.datas,
26 |           [],
27 |           name='electrum-personal-server',
28 |           debug=False,
29 |           bootloader_ignore_signals=False,
30 |           strip=False,
31 |           upx=True,
32 |           runtime_tmpdir=None,
33 |           console=True )
34 | 


--------------------------------------------------------------------------------
/config.ini_sample:
--------------------------------------------------------------------------------
  1 | # Electrum Personal Server configuration file
  2 | 
  3 | # Lines starting with # are called comments which are ignored.
  4 | # For a line to have effect it must not start with #
  5 | 
  6 | # The most important options are towards the top of the file
  7 | 
  8 | [master-public-keys]
  9 | # Add electrum wallet master public keys to this section
 10 | # In electrum then go Wallet -> Information to get the mpk
 11 | 
 12 | #any_name_works = xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF
 13 | 
 14 | # Multiple master public keys may be added by simply adding another line
 15 | #my_second_wallet = xpubanotherkey
 16 | 
 17 | # Multisig wallets use format `required-signatures [list of master pub keys]`
 18 | # this example is a 2-of-3 multisig wallet
 19 | #multisig_wallet = 2 xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF
 20 | 
 21 | 
 22 | [bitcoin-rpc]
 23 | host = 127.0.0.1
 24 | port = 8332
 25 | #add the bitcoin datadir to search for the .cookie file created by the
 26 | # node, which avoids the need to configure rpc_user/pass
 27 | #leave this option empty to have it look in the default location
 28 | datadir = 
 29 | #if you dont want to use the .cookie method with datadir, uncomment to config u/p here
 30 | #rpc_user =
 31 | #rpc_password =
 32 | 
 33 | #to be used with the multi-wallet feature
 34 | # see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.15.0.md#multi-wallet-support
 35 | # empty means default file, for when using a single wallet file
 36 | wallet_filename =
 37 | 
 38 | # how often in seconds to poll for new transactions when electrum not connected
 39 | poll_interval_listening = 30
 40 | # how often in seconds to poll for new transactions when electrum is connected
 41 | poll_interval_connected = 1
 42 | 
 43 | # Parameters for dealing with deterministic wallets
 44 | # how many addresses to import first time, should be big because if you import too little you may have to rescan again
 45 | initial_import_count = 1000
 46 | # number of unused addresses kept at the head of the wallet
 47 | gap_limit = 25
 48 | 
 49 | [electrum-server]
 50 | # 0.0.0.0 to accept connections from any IP
 51 | #127.0.0.1 to accept from only localhost
 52 | host = 127.0.0.1
 53 | port = 50002
 54 | 
 55 | # space-separated whitelist of IP addresses
 56 | # accepts CIDR notation eg 192.168.0.0/16 or 2a01:4f8:1f1::/120
 57 | # star (*) means all are accepted
 58 | # generally requires host binding (above) to be 0.0.0.0
 59 | ip_whitelist = *
 60 | 
 61 | #SSL certificate
 62 | #uses the default one, which is fine because by default nobody should be
 63 | # allowed to connect to your server or scan your packets
 64 | #to generate another certificate see https://github.com/spesmilo/electrum-server/blob/ce1b11d7f5f7a70a3b6cc7ec1d3e552436e54ffe/HOWTO.md#step-8-create-a-self-signed-ssl-cert
 65 | certfile = certs/cert.crt
 66 | keyfile = certs/cert.key
 67 | 
 68 | # Option for disabling the fee histogram calculation
 69 | # It improves server responsiveness but stops mempool-based Electrum features
 70 | # This is useful on low powered devices at times when the node mempool is large
 71 | disable_mempool_fee_histogram = false
 72 | 
 73 | # How often in seconds to update the mempool
 74 | # this mempool is used to calculate the mempool fee histogram
 75 | mempool_update_interval = 60
 76 | 
 77 | # Parameter for broadcasting unconfirmed transactions
 78 | # Options are:
 79 | # * tor-or-own-node  (use tor if tor is running locally, otherwise own-node)
 80 | # * own-node         (broadcast using the connected full node)
 81 | # * tor              (broadcast to random nodes over tor)
 82 | # * system <cmd> %s  (save transaction to file, and invoke system command
 83 | #                     with file path as parameter %s)
 84 | broadcast_method = tor-or-own-node
 85 | 
 86 | # For tor broadcasting (broadcast_method = tor) configure
 87 | # the tor proxy host and port below
 88 | tor_host = localhost
 89 | tor_port = 9050
 90 | 
 91 | # = Notes on tor broadcasting =
 92 | # If using tor broadcasting, make sure you set `walletbroadcast=0` in
 93 | #  your bitcoin.conf file
 94 | # If your full node is connected to the internet only via tor then you dont
 95 | #  need separate tor broadcasting
 96 | 
 97 | 
 98 | [watch-only-addresses]
 99 | #Add individual addresses to this section, for example paper wallets
100 | #Dont use this section for adding entire wallets, instead use the
101 | # above section `master-public-keys`
102 | 
103 | #addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n
104 | 
105 | # A space separated list is also accepted
106 | #my_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
107 | 
108 | # multiple addresses may also be added in separate lines (like master public keys above)
109 | #addr2 = 3anotheraddress
110 | 
111 | 
112 | [logging]
113 | # Section for configuring the logging output
114 | 
115 | # The log level. Logging messages less severe than this will not be displayed
116 | #options are 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
117 | #Information printed to the log file is always at level DEBUG
118 | log_level_stdout = INFO
119 | 
120 | # Location of log file, leave empty to use the default location in temp dir
121 | log_file_location =
122 | 
123 | # Whether to append to log file or delete and overwrite on startup
124 | append_log = false
125 | 
126 | # Format to use for logging messages
127 | #see docs https://docs.python.org/3/library/logging.html#formatter-objects
128 | log_format = %(levelname)s:%(asctime)s: %(message)s
129 | 
130 | 


--------------------------------------------------------------------------------
/contrib/electrumpersonalserver.confd:
--------------------------------------------------------------------------------
 1 | # Copyright 1999-2021 Gentoo Authors
 2 | # Distributed under the terms of the GNU General Public License v2
 3 | 
 4 | eps_user="bitcoin"
 5 | eps_group="bitcoin"
 6 | 
 7 | eps_config="/etc/electrum-personal-server.ini"
 8 | 
 9 | eps_args="${eps_config}"
10 | 


--------------------------------------------------------------------------------
/contrib/electrumpersonalserver.initd:
--------------------------------------------------------------------------------
 1 | #!/sbin/openrc-run
 2 | # Copyright 1999-2021 Gentoo Authors
 3 | # Distributed under the terms of the GNU General Public License v2
 4 | 
 5 | pidfile="/var/run/electrum-personal-server.pid"
 6 | command="/usr/bin/electrum-personal-server"
 7 | command_args="${eps_args}"
 8 | start_stop_daemon_args="-u ${eps_user} -b -m -p ${pidfile}"
 9 | 
10 | name="Electrum Personal Server"
11 | description="Connects to the bitcoind RPC"
12 | 
13 | depend() {
14 | 	need bitcoind
15 | }
16 | 
17 | start_pre() {
18 | 	if ! [ -e "${eps_config}" ]; then
19 | 		eerror ""
20 | 		eerror "Please create a configuration in ${eps_config}"
21 | 		eerror ""
22 | 		return 1
23 | 	fi
24 | }
25 | 


--------------------------------------------------------------------------------
/contrib/electrumpersonalserver.service:
--------------------------------------------------------------------------------
 1 | # Electrum Personal Server: systemd unit
 2 | # /etc/systemd/system/electrumpersonalserver.service
 3 | 
 4 | [Unit]
 5 | Description=Electrum Personal Server
 6 | After=bitcoind.service
 7 | Requires=bitcoind.service
 8 | 
 9 | [Service]
10 | ExecStart=/your-installation-path/server.py /your-workding-dir [optional arguments]
11 | User=bitcoin
12 | Group=bitcoin
13 | Type=simple
14 | Restart=on-failure
15 | RestartSec=60
16 | KillMode=process
17 | 
18 | [Install]
19 | WantedBy=multi-user.target
20 | 


--------------------------------------------------------------------------------
/docs/developer-notes.md:
--------------------------------------------------------------------------------
 1 | # Developer notes for Electrum Personal Server
 2 | 
 3 | Please keep lines under 80 characters in length and ideally don't add
 4 | any external dependencies to keep this as easy to install as possible.
 5 | 
 6 | The project tries to follow the [python style guide PEP 8](https://www.python.org/dev/peps/pep-0008/).
 7 | 
 8 | ## Naming
 9 | 
10 | Do not use the acronym EPS. Acronyms are not very user-friendly and are hard to
11 | search for.
12 | 
13 | ## Installing in developer mode
14 | 
15 | To seamlessly work on the codebase while using `pip`, you need to
16 | install in the `develop`/`editable` mode.  You can do that with:
17 | 
18 |     $ pip3 install --user -e /path/to/repo
19 | 
20 | `/path/to/repo` can also be a relative path, so if you are in the
21 | source directory, just use `.`.  This installs the scripts in the
22 | usual places, but imports the package from the source directory.  This
23 | way, any changes you make are immediately visible.
24 | 
25 | ## Maintainable code
26 | 
27 | Read the article [How To Write Unmaintainable Code](https://github.com/Droogans/unmaintainable-code/blob/master/README.md) and do the opposite of what it says.
28 | 
29 | ## Commits
30 | 
31 | Commits should be [atomic](https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention) and diffs should be easy to read.
32 | 
33 | Commit messages should be verbose by default consisting of a short subject line
34 | (50 chars max), a blank line and detailed explanatory text as separate
35 | paragraph(s), unless the title alone is self-explanatory (like "Corrected typo
36 | in server.py") in which case a single title line is sufficient. Commit messages
37 | should be helpful to people reading your code in the future, so explain the
38 | reasoning for your decisions. Further explanation
39 | [here](https://chris.beams.io/posts/git-commit/).
40 | 
41 | ## Testing
42 | 
43 | Electrum Personal Server also works on [testnet](https://en.bitcoin.it/wiki/Testnet),
44 | [regtest](https://bitcoin.org/en/glossary/regression-test-mode) and
45 | [signet](https://en.bitcoin.it/wiki/Signet). The Electrum wallet can be started
46 | in testnet mode with the command line flag `--testnet`, `--regtest` or `--signet`.
47 | 
48 | pytest is used for automated testing. On Debian-like systems install with
49 | `pip3 install pytest pytest-cov`
50 | 
51 | Run the tests with:
52 | 
53 |     $ PYTHONPATH=.:$PYTHONPATH pytest
54 | 
55 | Create the coverage report with:
56 | 
57 |     $ PYTHONPATH=.:$PYTHONPATH pytest --cov-report=html --cov
58 |     $ open htmlcov/index.html
59 | 
60 | If you have installed Electrum Personal Server with pip, there is no
61 | need to set `PYTHONPATH`.  You could also run the tests with:
62 | 
63 |     $ python3 setup.py test
64 | 
65 | ## Packaged binary release with pyinstaller
66 | 
67 | Pyinstaller is used to create the packaged binary releases. To build run:
68 | 
69 |     pyinstaller common.spec
70 | 
71 | This is best done on a virtual machine with the target OS installed. The
72 | `cert/` directory needs to be copied and for windows its helpful to run
73 | `unix2dos config.ini_sample` to convert the line endings.
74 | 
75 | 


--------------------------------------------------------------------------------
/docs/pubkeys/belcher.asc:
--------------------------------------------------------------------------------
 1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
 2 | Version: SKS 1.1.5
 3 | 
 4 | mQINBFPk74oBEACzBLjd+Z5z7eimqPuObFTaJCTXP7fgZjgVwt+q94VQ2wM0ctk/Ft9w2A92
 5 | f14T7PiHaVDjHxrcW+6sw2VI2f60T8Tjf+b4701hIybluWL8DntG9BW19bZLmjAj7zkgektl
 6 | YNDUrlYcQq2OEHm/MGk6Ajt2RA56aRKqoz22e+4ZA89gDgamxUAadul7AETSsgqOEUDI0FKR
 7 | FODzoH65w1ien/DLkG1f76jd0XA6AxrESJVO0JzvkTnJGElBcA37rYaMmDi4DhG2MY4u63VE
 8 | 8h6DyUXcRhmTZIAj+r+Ht+KMDiuiyQcKywCzzF/7Ui7YxqeAgjm5aPDU2E8X9Qd7cqHQzFM7
 9 | ZCqc9P6ENAk5a0JjHw0d0knApboSvkIJUB0j1xDIS0HaRlfHM4TPdOoDgnaXb7BvDfE+0zSz
10 | WkvAns9oJV6uWdnz5kllVCjgB/FXO4plyFCHhXikXjm1XuQyL8xV88OqgDFXwVhKrDL9Pknu
11 | sTchYm3BS2b5Xq1HQqToT3I2gRGTtDzZVZV0izCefJaDp1mf49k2cokDEfw9MroEj4A0Wfht
12 | 0J64pzlBYn/9zor5cZp/EAblLRDK6HKhSZArIiDR1RC7a6s7oTzmfn0suhKDdTzkbTAnDsPi
13 | Dokl58xoxz+JdYKjzVh98lpcvMPlbZ+LwIsgbdH4KZj7mVOsJwARAQABtB9DaHJpcyBCZWxj
14 | aGVyIDxmYWxzZUBlbWFpbC5jb20+iQEcBBMBAgAGBQJWMUYAAAoJELOuCfHpoxl61jUH/jdG
15 | SozE6+JCokJJ5Mx9HhPp/+5TgBHC00ykLe/Es3q84q1/mj3ovpoO44OTbJjIrZvR5k85c/gT
16 | Mn3aQQMjl3pI/fpugIub+Nq7v6lSO1akn/r8DEFLzr4MSCy3yofe307JvwVxn9EFTubgr1dn
17 | 8OAxshQwA+mSsoMgpuoKfgU7T/4x5Kjg11l1QiuMq20/4IglsSSo3vsvoSt7LEGf7NnpLk/b
18 | LRU8MatkYsgKGcuKs61MFyKkxv3mULsllObDqRxsmOKocMk7/rp/7ozMQNrtHB2MDgOQmrMJ
19 | wIVZqAHCFP7holgPACAscH26eq/KHU2BEETDgkM4ThvaNwUmIhCJAhwEEAECAAYFAlYe6GEA
20 | CgkQJHQyDqNnlYAhcg/8CJ/DZvGqfWNEASPGPYuZMEkNrCilpskPqyaD0jdXQvaJgFIWlQBV
21 | Wl+tknG9Iuz6l0kWfIMS/KIpzBxAoB8FC/TtIoJWGRCagFZHVjtaKr2oJQLqAqXiSRCadsWA
22 | TN16nfdipXuixi6xTVOhxvI4K2QKvQv2Qm3nx6NeyipeOUCe3UdtaPgQttKMt0BqhjMPTn1o
23 | oBDG0Whc28WdBJswa69S5VjkeFNrE8gSTDmqXasz4QwprGYN/A3wImzrh19IJnz5zdNW0WoN
24 | /Xf1zd5XctVSmhJ3Jdrch7vnAWfGI0oD3brYZzUTc6UJq/JaORXhqH7toxxc07YSg4Bj0j53
25 | m0LbaWmu0yut2Te8LU6CjMol1RFr57BRC2dOGtege0t6MjRdXptGlAKtNkMlr8ndEkCCrFIG
26 | C/UMtI+nSVdusVR35MJaHRXFlPNvxbHfPZhpMO7Otzq5yfk8lSOxDny9V/6929dRoMKP9zBy
27 | OhzJib8o2/ykv1zFd5aouwjD/jhIj2r4Xy5MdRac9MsLMz8A1hVHUUS69Gmn0csY857WpdFf
28 | R4thh6iqL2yd+ngXKveFHk06fm9MUd4vsEjQWW/PmY3FQlB/Gs25IjK3Di49V/LstrEqSfWk
29 | hWA4t23c6sinZSaIbCKcwARC/x5SpkB7JszB5HH0hDPFqQ+PC0lZyoqJAhwEEAEKAAYFAlZG
30 | pcgACgkQG5GE354Rdxi8Rw/+MbF00L6S95ftPwBqeuvY/ywccYXFuXb6epNOT0wEJnmDKIW4
31 | /ASjFFMofdYyrSSW/Iw5WKPZtnGxXBN1dx8YOhT7tGK0bGHYDKFmVAebfU/RNK5kJrn5TjXl
32 | sM8qmU8CVqsIL6rJdGC6F0TU4NTZxQVosQqUZrouRifbhMvWGKCpNNCGuwH+3FgoBqoiFwTw
33 | qpyQYOpUFNmVYKioljMumHPgIjAu79XsqwvzumAuBmYS9tu+ZJxEZvk1r73gQUX3Ub0Nlcp+
34 | EZyhKFUvM1MVaP8eeNZl+QZjIrkGOg6osKgvkTnx0y5aDmwaMJ8crsVPU8XV5GDjbPWcCZyl
35 | OX9CxyixC7hyCxlju4w1TxZV6RUaksst+9G1fDCYbyh6H7HZIPDg74R52MohdvRpoSV8Bymo
36 | DGWOVkgwVbB+Dj47nmerp5pHDChf3XXfnyzWgeR06jTTvNraUrulGnvsIstJW1UUFdQUM28E
37 | HahqOB6KMvSZcaNzjPXiR+hlfF/jqE1JPGfXvF29kPjbySjIzqp2MI9EmsQFdcm16LmjUykI
38 | JqZTr0o4/vmF237LoW1Mio7aGW7afbWpwteWui/OnhrI08Dh5kfkBBgwdgjJUumVfjYXknwT
39 | VYAw3keXH4AQn43Br9/4Q67DYDY/7Olgu2isvxy8+Jwu74lu57epG0PC4q+JAj4EEwECACgF
40 | AlPk74oCGwMFCRLMAwAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEO9zTqZ38xEpLyMP
41 | /0/19JhIhWOW0v1usAx8i7vAmfQfSVdNty4Iza5CK0ncE3Ejdztqrl0RvLbu7lR3nii2dwMm
42 | 5E9BUMqzofu3WsieK2AnUxND39pAYUBu0pyC1UgwVt4KXNNN9avhbt8Tjk0jsT0QA2CTkl+Z
43 | aHdT4og1gsXlG2rKdihyuw/rM5X4b0AcKkF6Z31M3rQKgwszIzNMcRbWhB99pUBdjwOC2ERg
44 | 7+h1Zgih3tVOYSbTNTVpLKw+ozA9Y2bxciXR/xJ8pDJmUBTx7V4IVUJ6eQTjgUoLmpA1hzZT
45 | F7ERi18JYKFMZzigcrEjJiZMIJ5+xAUu+DC2+U0dFcdIMpI9I1xFymXwMWk0QquYyWQnwzH5
46 | hHTkLJ2VNDRsjcUGiEkL5QeuMPkjeEDAlQLWj7JVI4R37M3dwbUQQIjCQRBKYGAbm3+V7ZF3
47 | N9c52l/2S7b/OgGmrTRHngnFQqV9Ezs49O/tf7UAoeTE7JJ0JnAaC92HZaG6qziRO+1yjTSA
48 | IUz18F3miLVEm4e3EeyPz79dTXlQi1uN6j3oIiUzlhR6hEs9PmbFlhaBgABEV5IVF42r9O7C
49 | r13wSiRSu0DKs9rLOeWLLndQl9YNXOX3blzQLPQdZo/Z9aP64VyP/wrRuGm25YlDLQ+iG3XP
50 | dokLVSMSCvVfPwjuN5ImYiUD2mVwnDaTh2FZuQINBFPk74oBEADIXEAzbwRDAty2vQKtCFhZ
51 | l8wrMCELD4TKtQn797i6IBDg7JeJ9GtlXqQtG6XdZtMHWSkDrCAJKE7Q3e/Dx8UbYKzpCmFV
52 | qAQfzJ9BSuhpXGAANg5Mscw5vsg1Lysoj+5E2YTptdNfbuhqoIRcszJypiW59p3uGvjZL9Pz
53 | M2Hmba+XpP/U+iPb+iuZWIrADEz7yp/F3wU0jhQqarbNkT2Znk+JIhfSpGAzTIK/FXf17H+e
54 | HHOi5MG+/gtvWe2mVH9swusfGwLOHIwoOBxfsCkab7LpgSw6/PD2tEJG4mkTiW2HxKGCNO01
55 | Md+SUEPpm3KZzurtt09ako4S87mUNDqrz4p8Q2/zrZw4uVYuTthHb6YdnU9caFYLuCDASR8Y
56 | mhejTHxf7OBQp7b70mPjISF8vQhsVgLPYnl35xZUgGS0jfGqTAtRla4BsTe+BgDiHzO1HqXX
57 | w1M+SIC+k1ARovhlGcDHbZZ2xDwaURZ3IpkFZstR01wyY98aLkULDac28LI3qS2/mZNkTXYS
58 | qC+nNhy5QZA8gxX0pOVI+6bc0sOrxAjqCtsbyI02kQHGYJA4rPb8G5gClTaHC3YTjh8vx7oJ
59 | EreuJ1xF2qm56DvmbLoqQ6qbydKIwRz+lUgfB0n3+q6K5Fp4L+1laVg8jANaBvxkT+DrrPfl
60 | YCC4xD4TB++ABQARAQABiQIlBBgBAgAPBQJT5O+KAhsMBQkSzAMAAAoJEO9zTqZ38xEppnAP
61 | /04jejeBWay3+YbHqFVCvL76G3x0VY0iIBI8G8PcTkRZupaLqaapIg5LoLGNTQ+dzFME8Je3
62 | PGj9bO4cZCD/G015DRPxvaUFJxkDD3eqbUhwdctR7nD9NqnphoOZJgIhkB21sfNh89bgkrty
63 | a9yO8XyOFMU4EQ4VVCROLX0btxpWi3T7AvV7YNEcNcB7kKKcSlosGXAd4S7/mPrBzd6uiFRm
64 | Ur0hsByWPiJlNBtqrcTKpReXn10E443GYvl50nHNWKyEhhAYjID1Y/VLKVe36Q7zY0SpkbY7
65 | F1cCXOO4nwLakAb6dsu0OjiFyyI38wBpf38kHsxdyVPmKh6sXjI2AoR1+rsubKexNR3zD8iX
66 | KnsAP4Fai6PI73iLyH14q+N/0+9LDFC4CNGnGd7tkkPy8Kww1cEdJt4QrYuLBK0cv/Djve+J
67 | ekMPncno4vFfprlG6aZH6uLFswft2AowlDsWXIOmGbeivoJKQYSKvUoDTLW+pA0W0eGC4Vqk
68 | BYwMpomNBA2C9lSY6jqFtmqa9QXU5Zx3oYDqPdY5R7d8Mld9uro/0mbGrriinzzt1gqyDvGN
69 | Cr/sozvDJFypddxsF2C821UM/4SfME0fJhj/eazhggcTkkfFD0tXjI9xaRhO9vVek0gVVutJ
70 | cB7IYdU1P5I7qiUWi9tYgLcDP3J6xozVpKhO
71 | =jSee
72 | -----END PGP PUBLIC KEY BLOCK-----
73 | 


--------------------------------------------------------------------------------
/docs/signed-donation-addresses.txt:
--------------------------------------------------------------------------------
 1 | -----BEGIN PGP SIGNED MESSAGE-----
 2 | Hash: SHA512
 3 | 
 4 | Bitcoin address for sending donations to the Electrum Personal Server project:
 5 | 
 6 | bc1qe74qzd256kxevq2gn7gmscs564lfk5tqrxqsuy
 7 | 
 8 | Always check signatures of addresses to avoid man-in-the-middle attacks
 9 | The GPG public key and fingerprint can be found at:
10 | * The Electrum Personal Server github repository in /docs/pubkeys/belcher.asc
11 | * https://github.com/chris-belcher
12 | * https://www.reddit.com/r/publickeyexchange/comments/3ti8vp/ubelcher_s_public_key/
13 | * https://twitter.com/chris_belcher_/status/559879313622061056
14 | * https://bitcointalk.org/index.php?action=profile;u=321816
15 | * https://en.bitcoin.it/wiki/User:Belcher
16 | * https://x0f.org/@belcher/105595837557120598
17 | 
18 | -----BEGIN PGP SIGNATURE-----
19 | 
20 | iQJEBAEBCgAuFiEECosDj14QzCeJv8//73NOpnfzESkFAmImWOcQHGZhbHNlQGVt
21 | YWlsLmNvbQAKCRDvc06md/MRKW29D/9QJMY4egYSlSUtKVUyE7AiWTQeG/ClWE2S
22 | oOOKrneoQ+dEXcUA5o1P5+I8i6h03cwTQraZyL3sN+01X65d6vv57Qp11B2OQb4h
23 | 6Xz4TYK7uUGc4oC/OkHPiM2XUsfH10YNZyYx6pvGTYURlzf7iyhysWaaGYHWmlmK
24 | ksTBNopx/S840ChT+h0M3zqQoEGodphZ27CTzv+ruaVhOL1X1H5Em6/H7VTp0yyu
25 | BL8pTvt6LP7Ca71bhDHllIOuAQy1OeWgcpeSn2kD9JNK47q8j+yX4G9BN2BxKEUx
26 | xPC6sUO/qtigN1X7E4EmlHBXlbB0mnyGPVQAeoetgwpz8/psay5MPTo+MYfP/t4i
27 | iDgELdqWoGnsd+nM89GIkTH+LjQounyzhKqYEsAubV8Vz3J1xiV+qyf2w2rvPY9g
28 | hJpMmJI3tXN/hGPv7b+hcQ+iV7oHXPtHta55w9DhCC9a29t/4Z7Pe2gthewklD48
29 | gLBZOr6ZLFbJVphqArFc/qfDElgOmLxxVPm9GvzFPi7Hc/2BJNmV+Nn3WZwrdl1w
30 | RmnqhQr3nEivB2zpBYtTwTPqP6OwhSE9BqqnM6dWoYssv6DqdpcMzrjp5skuC+JV
31 | YV0nitJdDi+7tYiouVCzaR/XuzXImwn0DoOJkEljZw1ytU+b/M4CzYjOJ8+XhEud
32 | ujzBkPVxVA==
33 | =FNv/
34 | -----END PGP SIGNATURE-----
35 | 
36 | 


--------------------------------------------------------------------------------
/electrum-personal-server-rescan.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | electrum-personal-server --rescan %1
3 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/__init__.py:
--------------------------------------------------------------------------------
1 | import electrumpersonalserver.bitcoin
2 | import electrumpersonalserver.server
3 | 
4 | __certfile__ = 'certs/cert.crt'
5 | __keyfile__ = 'certs/cert.key'
6 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/__init__.py:
--------------------------------------------------------------------------------
 1 | from electrumpersonalserver.bitcoin.py2specials import *
 2 | from electrumpersonalserver.bitcoin.py3specials import *
 3 | 
 4 | secp_present = False
 5 | try:
 6 |     import secp256k1
 7 | 
 8 |     secp_present = True
 9 |     from electrumpersonalserver.bitcoin.secp256k1_main import *
10 |     from electrumpersonalserver.bitcoin.secp256k1_transaction import *
11 |     from electrumpersonalserver.bitcoin.secp256k1_deterministic import *
12 | except (ImportError, AttributeError) as e:
13 |     from electrumpersonalserver.bitcoin.main import *
14 |     from electrumpersonalserver.bitcoin.deterministic import *
15 |     from electrumpersonalserver.bitcoin.transaction import *
16 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/deterministic.py:
--------------------------------------------------------------------------------
  1 | from electrumpersonalserver.bitcoin.main import *
  2 | import hmac
  3 | import hashlib
  4 | from binascii import hexlify
  5 | 
  6 | # Below code ASSUMES binary inputs and compressed pubkeys
  7 | MAINNET_PRIVATE = b'\x04\x88\xAD\xE4'
  8 | #MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
  9 | TESTNET_PRIVATE = b'\x04\x35\x83\x94'
 10 | #TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
 11 | PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE]
 12 | #PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC]
 13 | 
 14 | #updated for electrum's bip32 version bytes
 15 | #only public keys because electrum personal server only needs them
 16 | #https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
 17 | PUBLIC = [  b'\x04\x88\xb2\x1e', #mainnet p2pkh or p2sh xpub
 18 |             b'\x04\x9d\x7c\xb2', #mainnet p2wpkh-p2sh ypub
 19 |             b'\x02\x95\xb4\x3f', #mainnet p2wsh-p2sh Ypub
 20 |             b'\x04\xb2\x47\x46', #mainnet p2wpkh zpub
 21 |             b'\x02\xaa\x7e\xd3', #mainnet p2wsh Zpub
 22 |             b'\x04\x35\x87\xcf', #testnet p2pkh or p2sh tpub
 23 |             b'\x04\x4a\x52\x62', #testnet p2wpkh-p2sh upub
 24 |             b'\x02\x42\x89\xef', #testnet p2wsh-p2sh Upub
 25 |             b'\x04\x5f\x1c\xf6', #testnet p2wpkh vpub
 26 |             b'\x02\x57\x54\x83' #testnet p2wsh Vpub
 27 |         ]
 28 | 
 29 | # BIP32 child key derivation
 30 | 
 31 | def raw_bip32_ckd(rawtuple, i):
 32 |     vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple
 33 |     i = int(i)
 34 | 
 35 |     if vbytes in PRIVATE:
 36 |         priv = key
 37 |         pub = privtopub(key)
 38 |     else:
 39 |         pub = key
 40 | 
 41 |     if i >= 2**31:
 42 |         if vbytes in PUBLIC:
 43 |             raise ValueError("Can't do private derivation on public key!")
 44 |         I = hmac.new(chaincode, b'\x00' + priv[:32] + encode(i, 256, 4),
 45 |                      hashlib.sha512).digest()
 46 |     else:
 47 |         I = hmac.new(chaincode, pub + encode(i, 256, 4),
 48 |                      hashlib.sha512).digest()
 49 | 
 50 |     if vbytes in PRIVATE:
 51 |         newkey = add_privkeys(I[:32] + B'\x01', priv)
 52 |         fingerprint = bin_hash160(privtopub(key))[:4]
 53 |     if vbytes in PUBLIC:
 54 |         newkey = add_pubkeys(compress(privtopub(I[:32])), key)
 55 |         fingerprint = bin_hash160(key)[:4]
 56 | 
 57 |     return (vbytes, depth + 1, fingerprint, i, I[32:], newkey)
 58 | 
 59 | 
 60 | def bip32_serialize(rawtuple):
 61 |     vbytes, depth, fingerprint, i, chaincode, key = rawtuple
 62 |     i = encode(i, 256, 4)
 63 |     chaincode = encode(hash_to_int(chaincode), 256, 32)
 64 |     keydata = b'\x00' + key[:-1] if vbytes in PRIVATE else key
 65 |     bindata = vbytes + from_int_to_byte(
 66 |         depth % 256) + fingerprint + i + chaincode + keydata
 67 |     return changebase(bindata + bin_dbl_sha256(bindata)[:4], 256, 58)
 68 | 
 69 | 
 70 | def bip32_deserialize(data):
 71 |     dbin = changebase(data, 58, 256)
 72 |     if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]:
 73 |         raise ValueError("Invalid checksum")
 74 |     vbytes = dbin[0:4]
 75 |     depth = from_byte_to_int(dbin[4])
 76 |     fingerprint = dbin[5:9]
 77 |     i = decode(dbin[9:13], 256)
 78 |     chaincode = dbin[13:45]
 79 |     key = dbin[46:78] + b'\x01' if vbytes in PRIVATE else dbin[45:78]
 80 |     return (vbytes, depth, fingerprint, i, chaincode, key)
 81 | 
 82 | 
 83 | def raw_bip32_privtopub(rawtuple):
 84 |     vbytes, depth, fingerprint, i, chaincode, key = rawtuple
 85 |     newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC
 86 |     return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key))
 87 | 
 88 | 
 89 | def bip32_privtopub(data):
 90 |     return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data)))
 91 | 
 92 | 
 93 | def bip32_ckd(data, i):
 94 |     return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i))
 95 | 
 96 | 
 97 | def bip32_master_key(seed, vbytes=MAINNET_PRIVATE):
 98 |     I = hmac.new(
 99 |         from_string_to_bytes("Bitcoin seed"), seed, hashlib.sha512).digest()
100 |     return bip32_serialize((vbytes, 0, b'\x00' * 4, 0, I[32:], I[:32] + b'\x01'
101 |                            ))
102 | 
103 | 
104 | def bip32_bin_extract_key(data):
105 |     return bip32_deserialize(data)[-1]
106 | 
107 | 
108 | def bip32_extract_key(data):
109 |     return safe_hexlify(bip32_deserialize(data)[-1])
110 | 
111 | 
112 | def bip32_descend(*args):
113 |     if len(args) == 2:
114 |         key, path = args
115 |     else:
116 |         key, path = args[0], map(int, args[1:])
117 |     for p in path:
118 |         key = bip32_ckd(key, p)
119 |     return bip32_extract_key(key)
120 | 
121 | # electrum
122 | def electrum_stretch(seed):
123 |     return slowsha(seed)
124 | 
125 | # Accepts seed or stretched seed, returns master public key
126 | 
127 | def electrum_mpk(seed):
128 |     if len(seed) == 32:
129 |         seed = electrum_stretch(seed)
130 |     return privkey_to_pubkey(seed)[2:]
131 | 
132 | # Accepts (seed or stretched seed), index and secondary index
133 | # (conventionally 0 for ordinary addresses, 1 for change) , returns privkey
134 | 
135 | 
136 | def electrum_privkey(seed, n, for_change=0):
137 |     if len(seed) == 32:
138 |         seed = electrum_stretch(seed)
139 |     mpk = electrum_mpk(seed)
140 |     offset = dbl_sha256(from_int_representation_to_bytes(n)+b':'+
141 |         from_int_representation_to_bytes(for_change)+b':'+
142 |         binascii.unhexlify(mpk))
143 |     return add_privkeys(seed, offset)
144 | 
145 | # Accepts (seed or stretched seed or master pubkey), index and secondary index
146 | # (conventionally 0 for ordinary addresses, 1 for change) , returns pubkey
147 | 
148 | def electrum_pubkey(masterkey, n, for_change=0):
149 |     if len(masterkey) == 32:
150 |         mpk = electrum_mpk(electrum_stretch(masterkey))
151 |     elif len(masterkey) == 64:
152 |         mpk = electrum_mpk(masterkey)
153 |     else:
154 |         mpk = masterkey
155 |     bin_mpk = encode_pubkey(mpk, 'bin_electrum')
156 |     offset = bin_dbl_sha256(from_int_representation_to_bytes(n)+b':'+
157 |         from_int_representation_to_bytes(for_change)+b':'+bin_mpk)
158 |     return add_pubkeys('04'+mpk, privtopub(offset))
159 | 
160 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/main.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/python
  2 | from .py2specials import *
  3 | from .py3specials import *
  4 | import binascii
  5 | import hashlib
  6 | import re
  7 | import sys
  8 | import os
  9 | import base64
 10 | import time
 11 | import random
 12 | import hmac
 13 | 
 14 | is_python2 = sys.version_info.major == 2
 15 | 
 16 | # Elliptic curve parameters (secp256k1)
 17 | 
 18 | P = 2**256 - 2**32 - 977
 19 | N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
 20 | A = 0
 21 | B = 7
 22 | Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
 23 | Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
 24 | G = (Gx, Gy)
 25 | 
 26 | # Extended Euclidean Algorithm
 27 | def inv(a, n):
 28 |     lm, hm = 1, 0
 29 |     low, high = a % n, n
 30 |     while low > 1:
 31 |         r = high // low
 32 |         nm, new = hm - lm * r, high - low * r
 33 |         lm, low, hm, high = nm, new, lm, low
 34 |     return lm % n
 35 | 
 36 | # Elliptic curve Jordan form functions
 37 | # P = (m, n, p, q) where m/n = x, p/q = y
 38 | 
 39 | def isinf(p):
 40 |     return p[0] == 0 and p[1] == 0
 41 | 
 42 | 
 43 | def jordan_isinf(p):
 44 |     return p[0][0] == 0 and p[1][0] == 0
 45 | 
 46 | 
 47 | def mulcoords(c1, c2):
 48 |     return (c1[0] * c2[0] % P, c1[1] * c2[1] % P)
 49 | 
 50 | 
 51 | def mul_by_const(c, v):
 52 |     return (c[0] * v % P, c[1])
 53 | 
 54 | 
 55 | def addcoords(c1, c2):
 56 |     return ((c1[0] * c2[1] + c2[0] * c1[1]) % P, c1[1] * c2[1] % P)
 57 | 
 58 | 
 59 | def subcoords(c1, c2):
 60 |     return ((c1[0] * c2[1] - c2[0] * c1[1]) % P, c1[1] * c2[1] % P)
 61 | 
 62 | 
 63 | def invcoords(c):
 64 |     return (c[1], c[0])
 65 | 
 66 | 
 67 | def jordan_add(a, b):
 68 |     if jordan_isinf(a):
 69 |         return b
 70 |     if jordan_isinf(b):
 71 |         return a
 72 | 
 73 |     if (a[0][0] * b[0][1] - b[0][0] * a[0][1]) % P == 0:
 74 |         if (a[1][0] * b[1][1] - b[1][0] * a[1][1]) % P == 0:
 75 |             return jordan_double(a)
 76 |         else:
 77 |             return ((0, 1), (0, 1))
 78 |     xdiff = subcoords(b[0], a[0])
 79 |     ydiff = subcoords(b[1], a[1])
 80 |     m = mulcoords(ydiff, invcoords(xdiff))
 81 |     x = subcoords(subcoords(mulcoords(m, m), a[0]), b[0])
 82 |     y = subcoords(mulcoords(m, subcoords(a[0], x)), a[1])
 83 |     return (x, y)
 84 | 
 85 | 
 86 | def jordan_double(a):
 87 |     if jordan_isinf(a):
 88 |         return ((0, 1), (0, 1))
 89 |     num = addcoords(mul_by_const(mulcoords(a[0], a[0]), 3), (A, 1))
 90 |     den = mul_by_const(a[1], 2)
 91 |     m = mulcoords(num, invcoords(den))
 92 |     x = subcoords(mulcoords(m, m), mul_by_const(a[0], 2))
 93 |     y = subcoords(mulcoords(m, subcoords(a[0], x)), a[1])
 94 |     return (x, y)
 95 | 
 96 | 
 97 | def jordan_multiply(a, n):
 98 |     if jordan_isinf(a) or n == 0:
 99 |         return ((0, 0), (0, 0))
100 |     if n == 1:
101 |         return a
102 |     if n < 0 or n >= N:
103 |         return jordan_multiply(a, n % N)
104 |     if (n % 2) == 0:
105 |         return jordan_double(jordan_multiply(a, n // 2))
106 |     if (n % 2) == 1:
107 |         return jordan_add(jordan_double(jordan_multiply(a, n // 2)), a)
108 | 
109 | 
110 | def to_jordan(p):
111 |     return ((p[0], 1), (p[1], 1))
112 | 
113 | 
114 | def from_jordan(p):
115 |     return (p[0][0] * inv(p[0][1], P) % P, p[1][0] * inv(p[1][1], P) % P)
116 | 
117 | def fast_multiply(a, n):
118 |     return from_jordan(jordan_multiply(to_jordan(a), n))
119 | 
120 | 
121 | def fast_add(a, b):
122 |     return from_jordan(jordan_add(to_jordan(a), to_jordan(b)))
123 | 
124 | # Functions for handling pubkey and privkey formats
125 | 
126 | 
127 | def get_pubkey_format(pub):
128 |     if is_python2:
129 |         two = '\x02'
130 |         three = '\x03'
131 |         four = '\x04'
132 |     else:
133 |         two = 2
134 |         three = 3
135 |         four = 4
136 | 
137 |     if isinstance(pub, (tuple, list)): return 'decimal'
138 |     elif len(pub) == 65 and pub[0] == four: return 'bin'
139 |     elif len(pub) == 130 and pub[0:2] == '04': return 'hex'
140 |     elif len(pub) == 33 and pub[0] in [two, three]: return 'bin_compressed'
141 |     elif len(pub) == 66 and pub[0:2] in ['02', '03']: return 'hex_compressed'
142 |     elif len(pub) == 64: return 'bin_electrum'
143 |     elif len(pub) == 128: return 'hex_electrum'
144 |     else: raise Exception("Pubkey not in recognized format")
145 | 
146 | 
147 | def encode_pubkey(pub, formt):
148 |     if not isinstance(pub, (tuple, list)):
149 |         pub = decode_pubkey(pub)
150 |     if formt == 'decimal': return pub
151 |     elif formt == 'bin':
152 |         return b'\x04' + encode(pub[0], 256, 32) + encode(pub[1], 256, 32)
153 |     elif formt == 'bin_compressed':
154 |         return from_int_to_byte(2 + (pub[1] % 2)) + encode(pub[0], 256, 32)
155 |     elif formt == 'hex':
156 |         return '04' + encode(pub[0], 16, 64) + encode(pub[1], 16, 64)
157 |     elif formt == 'hex_compressed':
158 |         return '0' + str(2 + (pub[1] % 2)) + encode(pub[0], 16, 64)
159 |     elif formt == 'bin_electrum':
160 |         return encode(pub[0], 256, 32) + encode(pub[1], 256, 32)
161 |     elif formt == 'hex_electrum':
162 |         return encode(pub[0], 16, 64) + encode(pub[1], 16, 64)
163 |     else:
164 |         raise Exception("Invalid format!")
165 | 
166 | 
167 | def decode_pubkey(pub, formt=None):
168 |     if not formt: formt = get_pubkey_format(pub)
169 |     if formt == 'decimal': return pub
170 |     elif formt == 'bin':
171 |         return (decode(pub[1:33], 256), decode(pub[33:65], 256))
172 |     elif formt == 'bin_compressed':
173 |         x = decode(pub[1:33], 256)
174 |         beta = pow(int(x * x * x + A * x + B), int((P + 1) // 4), int(P))
175 |         y = (P - beta) if ((beta + from_byte_to_int(pub[0])) % 2) else beta
176 |         return (x, y)
177 |     elif formt == 'hex':
178 |         return (decode(pub[2:66], 16), decode(pub[66:130], 16))
179 |     elif formt == 'hex_compressed':
180 |         return decode_pubkey(safe_from_hex(pub), 'bin_compressed')
181 |     elif formt == 'bin_electrum':
182 |         return (decode(pub[:32], 256), decode(pub[32:64], 256))
183 |     elif formt == 'hex_electrum':
184 |         return (decode(pub[:64], 16), decode(pub[64:128], 16))
185 |     else:
186 |         raise Exception("Invalid format!")
187 | 
188 | 
189 | def get_privkey_format(priv):
190 |     if isinstance(priv, int_types): return 'decimal'
191 |     elif len(priv) == 32: return 'bin'
192 |     elif len(priv) == 33: return 'bin_compressed'
193 |     elif len(priv) == 64: return 'hex'
194 |     elif len(priv) == 66: return 'hex_compressed'
195 |     else:
196 |         bin_p = b58check_to_bin(priv)
197 |         if len(bin_p) == 32: return 'wif'
198 |         elif len(bin_p) == 33: return 'wif_compressed'
199 |         else: raise Exception("WIF does not represent privkey")
200 | 
201 | 
202 | def encode_privkey(priv, formt, vbyte=0):
203 |     if not isinstance(priv, int_types):
204 |         return encode_privkey(decode_privkey(priv), formt, vbyte)
205 |     if formt == 'decimal': return priv
206 |     elif formt == 'bin': return encode(priv, 256, 32)
207 |     elif formt == 'bin_compressed': return encode(priv, 256, 32) + b'\x01'
208 |     elif formt == 'hex': return encode(priv, 16, 64)
209 |     elif formt == 'hex_compressed': return encode(priv, 16, 64) + '01'
210 |     elif formt == 'wif':
211 |         return bin_to_b58check(encode(priv, 256, 32), 128 + int(vbyte))
212 |     elif formt == 'wif_compressed':
213 |         return bin_to_b58check(
214 |             encode(priv, 256, 32) + b'\x01', 128 + int(vbyte))
215 |     else:
216 |         raise Exception("Invalid format!")
217 | 
218 | 
219 | def decode_privkey(priv, formt=None):
220 |     if not formt: formt = get_privkey_format(priv)
221 |     if formt == 'decimal': return priv
222 |     elif formt == 'bin': return decode(priv, 256)
223 |     elif formt == 'bin_compressed': return decode(priv[:32], 256)
224 |     elif formt == 'hex': return decode(priv, 16)
225 |     elif formt == 'hex_compressed': return decode(priv[:64], 16)
226 |     elif formt == 'wif': return decode(b58check_to_bin(priv), 256)
227 |     elif formt == 'wif_compressed':
228 |         return decode(b58check_to_bin(priv)[:32], 256)
229 |     else:
230 |         raise Exception("WIF does not represent privkey")
231 | 
232 | 
233 | def add_pubkeys(p1, p2):
234 |     f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2)
235 |     return encode_pubkey(
236 |         fast_add(
237 |             decode_pubkey(p1, f1), decode_pubkey(p2, f2)), f1)
238 | 
239 | 
240 | def add_privkeys(p1, p2):
241 |     f1, f2 = get_privkey_format(p1), get_privkey_format(p2)
242 |     return encode_privkey(
243 |         (decode_privkey(p1, f1) + decode_privkey(p2, f2)) % N, f1)
244 | 
245 | 
246 | def multiply(pubkey, privkey):
247 |     f1, f2 = get_pubkey_format(pubkey), get_privkey_format(privkey)
248 |     pubkey, privkey = decode_pubkey(pubkey, f1), decode_privkey(privkey, f2)
249 |     # http://safecurves.cr.yp.to/twist.html
250 |     if not isinf(pubkey) and (
251 |             pubkey[0]**3 + B - pubkey[1] * pubkey[1]) % P != 0:
252 |         raise Exception("Point not on curve")
253 |     return encode_pubkey(fast_multiply(pubkey, privkey), f1)
254 | 
255 | 
256 | def divide(pubkey, privkey):
257 |     factor = inv(decode_privkey(privkey), N)
258 |     return multiply(pubkey, factor)
259 | 
260 | 
261 | def compress(pubkey):
262 |     f = get_pubkey_format(pubkey)
263 |     if 'compressed' in f: return pubkey
264 |     elif f == 'bin':
265 |         return encode_pubkey(decode_pubkey(pubkey, f), 'bin_compressed')
266 |     elif f == 'hex' or f == 'decimal':
267 |         return encode_pubkey(decode_pubkey(pubkey, f), 'hex_compressed')
268 | 
269 | 
270 | def decompress(pubkey):
271 |     f = get_pubkey_format(pubkey)
272 |     if 'compressed' not in f: return pubkey
273 |     elif f == 'bin_compressed':
274 |         return encode_pubkey(decode_pubkey(pubkey, f), 'bin')
275 |     elif f == 'hex_compressed' or f == 'decimal':
276 |         return encode_pubkey(decode_pubkey(pubkey, f), 'hex')
277 | 
278 | 
279 | def privkey_to_pubkey(privkey):
280 |     f = get_privkey_format(privkey)
281 |     privkey = decode_privkey(privkey, f)
282 |     if privkey >= N:
283 |         raise Exception("Invalid privkey")
284 |     if f in ['bin', 'bin_compressed', 'hex', 'hex_compressed', 'decimal']:
285 |         return encode_pubkey(fast_multiply(G, privkey), f)
286 |     else:
287 |         return encode_pubkey(fast_multiply(G, privkey), f.replace('wif', 'hex'))
288 | 
289 | 
290 | privtopub = privkey_to_pubkey
291 | 
292 | 
293 | def privkey_to_address(priv, magicbyte=0):
294 |     return pubkey_to_address(privkey_to_pubkey(priv), magicbyte)
295 | 
296 | 
297 | privtoaddr = privkey_to_address
298 | 
299 | 
300 | def neg_pubkey(pubkey):
301 |     f = get_pubkey_format(pubkey)
302 |     pubkey = decode_pubkey(pubkey, f)
303 |     return encode_pubkey((pubkey[0], (P - pubkey[1]) % P), f)
304 | 
305 | 
306 | def neg_privkey(privkey):
307 |     f = get_privkey_format(privkey)
308 |     privkey = decode_privkey(privkey, f)
309 |     return encode_privkey((N - privkey) % N, f)
310 | 
311 | 
312 | def subtract_pubkeys(p1, p2):
313 |     f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2)
314 |     k2 = decode_pubkey(p2, f2)
315 |     return encode_pubkey(
316 |         fast_add(
317 |             decode_pubkey(p1, f1), (k2[0], (P - k2[1]) % P)), f1)
318 | 
319 | 
320 | # Hashes
321 | 
322 | 
323 | def bin_hash160(string):
324 |     intermed = hashlib.sha256(string).digest()
325 |     digest = ''
326 |     digest = hashlib.new('ripemd160', intermed).digest()
327 |     return digest
328 | 
329 | 
330 | def hash160(string):
331 |     return safe_hexlify(bin_hash160(string))
332 | 
333 | 
334 | def bin_sha256(string):
335 |     binary_data = string if isinstance(string, bytes) else bytes(string,
336 |                                                                  'utf-8')
337 |     return hashlib.sha256(binary_data).digest()
338 | 
339 | 
340 | def sha256(string):
341 |     return bytes_to_hex_string(bin_sha256(string))
342 | 
343 | 
344 | def bin_ripemd160(string):
345 |     digest = hashlib.new('ripemd160', string).digest()
346 |     return digest
347 | 
348 | 
349 | def ripemd160(string):
350 |     return safe_hexlify(bin_ripemd160(string))
351 | 
352 | 
353 | def bin_dbl_sha256(s):
354 |     bytes_to_hash = from_string_to_bytes(s)
355 |     return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest()
356 | 
357 | 
358 | def dbl_sha256(string):
359 |     return safe_hexlify(bin_dbl_sha256(string))
360 | 
361 | 
362 | def bin_slowsha(string):
363 |     string = from_string_to_bytes(string)
364 |     orig_input = string
365 |     for i in range(100000):
366 |         string = hashlib.sha256(string + orig_input).digest()
367 |     return string
368 | 
369 | 
370 | def slowsha(string):
371 |     return safe_hexlify(bin_slowsha(string))
372 | 
373 | 
374 | def hash_to_int(x):
375 |     if len(x) in [40, 64]:
376 |         return decode(x, 16)
377 |     return decode(x, 256)
378 | 
379 | 
380 | def num_to_var_int(x):
381 |     x = int(x)
382 |     if x < 253: return from_int_to_byte(x)
383 |     elif x < 65536: return from_int_to_byte(253) + encode(x, 256, 2)[::-1]
384 |     elif x < 4294967296: return from_int_to_byte(254) + encode(x, 256, 4)[::-1]
385 |     else: return from_int_to_byte(255) + encode(x, 256, 8)[::-1]
386 | 
387 | 
388 | # WTF, Electrum?
389 | def electrum_sig_hash(message):
390 |     padded = b"\x18Bitcoin Signed Message:\n" + num_to_var_int(len(
391 |         message)) + from_string_to_bytes(message)
392 |     return bin_dbl_sha256(padded)
393 | 
394 | # Encodings
395 | 
396 | def b58check_to_bin(inp):
397 |     leadingzbytes = len(re.match('^1*', inp).group(0))
398 |     data = b'\x00' * leadingzbytes + changebase(inp, 58, 256)
399 |     assert bin_dbl_sha256(data[:-4])[:4] == data[-4:]
400 |     return data[1:-4]
401 | 
402 | 
403 | def get_version_byte(inp):
404 |     leadingzbytes = len(re.match('^1*', inp).group(0))
405 |     data = b'\x00' * leadingzbytes + changebase(inp, 58, 256)
406 |     assert bin_dbl_sha256(data[:-4])[:4] == data[-4:]
407 |     return ord(data[0])
408 | 
409 | 
410 | def hex_to_b58check(inp, magicbyte=0):
411 |     return bin_to_b58check(binascii.unhexlify(inp), magicbyte)
412 | 
413 | 
414 | def b58check_to_hex(inp):
415 |     return safe_hexlify(b58check_to_bin(inp))
416 | 
417 | 
418 | def pubkey_to_address(pubkey, magicbyte=0):
419 |     if isinstance(pubkey, (list, tuple)):
420 |         pubkey = encode_pubkey(pubkey, 'bin')
421 |     if len(pubkey) in [66, 130]:
422 |         return bin_to_b58check(
423 |             bin_hash160(binascii.unhexlify(pubkey)), magicbyte)
424 |     return bin_to_b58check(bin_hash160(pubkey), magicbyte)
425 | 
426 | 
427 | pubtoaddr = pubkey_to_address
428 | 
429 | # EDCSA
430 | 
431 | 
432 | def encode_sig(v, r, s):
433 |     vb, rb, sb = from_int_to_byte(v), encode(r, 256), encode(s, 256)
434 | 
435 |     result = base64.b64encode(vb + b'\x00' * (32 - len(rb)) + rb + b'\x00' * (
436 |         32 - len(sb)) + sb)
437 |     return result if is_python2 else str(result, 'utf-8')
438 | 
439 | 
440 | def decode_sig(sig):
441 |     bytez = base64.b64decode(sig)
442 |     return from_byte_to_int(bytez[0]), decode(bytez[1:33], 256), decode(
443 |         bytez[33:], 256)
444 | 
445 | # https://tools.ietf.org/html/rfc6979#section-3.2
446 | 
447 | 
448 | def deterministic_generate_k(msghash, priv):
449 |     v = b'\x01' * 32
450 |     k = b'\x00' * 32
451 |     priv = encode_privkey(priv, 'bin')
452 |     msghash = encode(hash_to_int(msghash), 256, 32)
453 |     k = hmac.new(k, v + b'\x00' + priv + msghash, hashlib.sha256).digest()
454 |     v = hmac.new(k, v, hashlib.sha256).digest()
455 |     k = hmac.new(k, v + b'\x01' + priv + msghash, hashlib.sha256).digest()
456 |     v = hmac.new(k, v, hashlib.sha256).digest()
457 |     return decode(hmac.new(k, v, hashlib.sha256).digest(), 256)
458 | 
459 | 
460 | def ecdsa_raw_sign(msghash, priv):
461 | 
462 |     z = hash_to_int(msghash)
463 |     k = deterministic_generate_k(msghash, priv)
464 | 
465 |     r, y = fast_multiply(G, k)
466 |     s = inv(k, N) * (z + r * decode_privkey(priv)) % N
467 | 
468 |     return 27 + (y % 2), r, s
469 | 
470 | 
471 | def ecdsa_sign(msg, priv):
472 |     return encode_sig(*ecdsa_raw_sign(electrum_sig_hash(msg), priv))
473 | 
474 | def ecdsa_raw_verify(msghash, vrs, pub):
475 |     v, r, s = vrs
476 | 
477 |     w = inv(s, N)
478 |     z = hash_to_int(msghash)
479 | 
480 |     u1, u2 = z * w % N, r * w % N
481 |     x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2))
482 | 
483 |     return r == x
484 | 
485 | def ecdsa_verify(msg, sig, pub):
486 |     return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub)
487 | 
488 | def estimate_tx_size(ins, outs, txtype='p2pkh'):
489 |     '''Estimate transaction size.
490 |     Assuming p2pkh:
491 |     out: 8+1+3+2+20=34, in: 1+32+4+1+1+~73+1+1+33=147,
492 |     ver:4,seq:4, +2 (len in,out)
493 |     total ~= 34*len_out + 147*len_in + 10 (sig sizes vary slightly)
494 |     '''
495 |     if txtype=='p2pkh':
496 |         return 10 + ins*147 +34*outs
497 |     else:
498 |         raise NotImplementedError("Non p2pkh transaction size estimation not"+
499 |                                   "yet implemented")
500 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/py2specials.py:
--------------------------------------------------------------------------------
 1 | import sys, re
 2 | import binascii
 3 | import os
 4 | import hashlib
 5 | 
 6 | if sys.version_info.major == 2:
 7 |     string_types = (str, unicode)
 8 |     string_or_bytes_types = string_types
 9 |     int_types = (int, float, long)
10 | 
11 |     # Base switching
12 |     code_strings = {
13 |         2: '01',
14 |         10: '0123456789',
15 |         16: '0123456789abcdef',
16 |         32: 'abcdefghijklmnopqrstuvwxyz234567',
17 |         58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
18 |         256: ''.join([chr(x) for x in range(256)])
19 |     }
20 | 
21 |     def bin_dbl_sha256(s):
22 |         bytes_to_hash = from_string_to_bytes(s)
23 |         return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest()
24 | 
25 |     def lpad(msg, symbol, length):
26 |         if len(msg) >= length:
27 |             return msg
28 |         return symbol * (length - len(msg)) + msg
29 | 
30 |     def get_code_string(base):
31 |         if base in code_strings:
32 |             return code_strings[base]
33 |         else:
34 |             raise ValueError("Invalid base!")
35 | 
36 |     def changebase(string, frm, to, minlen=0):
37 |         if frm == to:
38 |             return lpad(string, get_code_string(frm)[0], minlen)
39 |         return encode(decode(string, frm), to, minlen)
40 | 
41 |     def bin_to_b58check(inp, magicbyte=0):
42 |         inp_fmtd = chr(int(magicbyte)) + inp
43 |         leadingzbytes = len(re.match('^\x00*', inp_fmtd).group(0))
44 |         checksum = bin_dbl_sha256(inp_fmtd)[:4]
45 |         return '1' * leadingzbytes + changebase(inp_fmtd + checksum, 256, 58)
46 | 
47 |     def bytes_to_hex_string(b):
48 |         return b.encode('hex')
49 | 
50 |     def safe_from_hex(s):
51 |         return s.decode('hex')
52 | 
53 |     def from_int_to_byte(a):
54 |         return chr(a)
55 | 
56 |     def from_byte_to_int(a):
57 |         return ord(a)
58 | 
59 |     def from_string_to_bytes(a):
60 |         return a
61 | 
62 |     def safe_hexlify(a):
63 |         return binascii.hexlify(a)
64 | 
65 |     def encode(val, base, minlen=0):
66 |         base, minlen = int(base), int(minlen)
67 |         code_string = get_code_string(base)
68 |         result = ""
69 |         while val > 0:
70 |             result = code_string[val % base] + result
71 |             val //= base
72 |         return code_string[0] * max(minlen - len(result), 0) + result
73 | 
74 |     def decode(string, base):
75 |         base = int(base)
76 |         code_string = get_code_string(base)
77 |         result = 0
78 |         if base == 16:
79 |             string = string.lower()
80 |         while len(string) > 0:
81 |             result *= base
82 |             result += code_string.find(string[0])
83 |             string = string[1:]
84 |         return result
85 | 
86 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/py3specials.py:
--------------------------------------------------------------------------------
  1 | import sys, os
  2 | import binascii
  3 | import hashlib
  4 | 
  5 | if sys.version_info.major == 3:
  6 |     string_types = (str)
  7 |     string_or_bytes_types = (str, bytes)
  8 |     int_types = (int, float)
  9 |     # Base switching
 10 |     code_strings = {
 11 |         2: '01',
 12 |         10: '0123456789',
 13 |         16: '0123456789abcdef',
 14 |         32: 'abcdefghijklmnopqrstuvwxyz234567',
 15 |         58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
 16 |         256: ''.join([chr(x) for x in range(256)])
 17 |     }
 18 | 
 19 |     def bin_dbl_sha256(s):
 20 |         bytes_to_hash = from_string_to_bytes(s)
 21 |         return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest()
 22 | 
 23 |     def lpad(msg, symbol, length):
 24 |         if len(msg) >= length:
 25 |             return msg
 26 |         return symbol * (length - len(msg)) + msg
 27 | 
 28 |     def get_code_string(base):
 29 |         if base in code_strings:
 30 |             return code_strings[base]
 31 |         else:
 32 |             raise ValueError("Invalid base!")
 33 | 
 34 |     def changebase(string, frm, to, minlen=0):
 35 |         if frm == to:
 36 |             return lpad(string, get_code_string(frm)[0], minlen)
 37 |         return encode(decode(string, frm), to, minlen)
 38 | 
 39 |     def bin_to_b58check(inp, magicbyte=0):
 40 |         inp_fmtd = from_int_to_byte(int(magicbyte)) + inp
 41 | 
 42 |         leadingzbytes = 0
 43 |         for x in inp_fmtd:
 44 |             if x != 0:
 45 |                 break
 46 |             leadingzbytes += 1
 47 | 
 48 |         checksum = bin_dbl_sha256(inp_fmtd)[:4]
 49 |         return '1' * leadingzbytes + changebase(inp_fmtd + checksum, 256, 58)
 50 | 
 51 |     def bytes_to_hex_string(b):
 52 |         if isinstance(b, str):
 53 |             return b
 54 | 
 55 |         return ''.join('{:02x}'.format(y) for y in b)
 56 | 
 57 |     def safe_from_hex(s):
 58 |         return bytes.fromhex(s)
 59 | 
 60 |     def from_int_to_byte(a):
 61 |         return bytes([a])
 62 | 
 63 |     def from_byte_to_int(a):
 64 |         return a
 65 | 
 66 |     def from_string_to_bytes(a):
 67 |         return a if isinstance(a, bytes) else bytes(a, 'utf-8')
 68 | 
 69 |     def safe_hexlify(a):
 70 |         return str(binascii.hexlify(a), 'utf-8')
 71 | 
 72 |     def encode(val, base, minlen=0):
 73 |         base, minlen = int(base), int(minlen)
 74 |         code_string = get_code_string(base)
 75 |         result_bytes = bytes()
 76 |         while val > 0:
 77 |             curcode = code_string[val % base]
 78 |             result_bytes = bytes([ord(curcode)]) + result_bytes
 79 |             val //= base
 80 | 
 81 |         pad_size = minlen - len(result_bytes)
 82 | 
 83 |         padding_element = b'\x00' if base == 256 else b'1' \
 84 |             if base == 58 else b'0'
 85 |         if (pad_size > 0):
 86 |             result_bytes = padding_element * pad_size + result_bytes
 87 | 
 88 |         result_string = ''.join([chr(y) for y in result_bytes])
 89 |         result = result_bytes if base == 256 else result_string
 90 | 
 91 |         return result
 92 | 
 93 |     def decode(string, base):
 94 |         if base == 256 and isinstance(string, str):
 95 |             string = bytes(bytearray.fromhex(string))
 96 |         base = int(base)
 97 |         code_string = get_code_string(base)
 98 |         result = 0
 99 |         if base == 256:
100 | 
101 |             def extract(d, cs):
102 |                 return d
103 |         else:
104 | 
105 |             def extract(d, cs):
106 |                 return cs.find(d if isinstance(d, str) else chr(d))
107 | 
108 |         if base == 16:
109 |             string = string.lower()
110 |         while len(string) > 0:
111 |             result *= base
112 |             result += extract(string[0], code_string)
113 |             string = string[1:]
114 |         return result
115 | 
116 |     def from_int_representation_to_bytes(a):
117 |         return bytes(str(a), 'utf-8')
118 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/secp256k1_deterministic.py:
--------------------------------------------------------------------------------
 1 | from electrumpersonalserver.bitcoin.secp256k1_main import *
 2 | import hmac
 3 | import hashlib
 4 | from binascii import hexlify
 5 | 
 6 | # Below code ASSUMES binary inputs and compressed pubkeys
 7 | MAINNET_PRIVATE = b'\x04\x88\xAD\xE4'
 8 | MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
 9 | TESTNET_PRIVATE = b'\x04\x35\x83\x94'
10 | TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
11 | PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE]
12 | PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC]
13 | 
14 | # BIP32 child key derivation
15 | 
16 | def raw_bip32_ckd(rawtuple, i):
17 |     vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple
18 |     i = int(i)
19 | 
20 |     if vbytes in PRIVATE:
21 |         priv = key
22 |         pub = privtopub(key, False)
23 |     else:
24 |         pub = key
25 | 
26 |     if i >= 2**31:
27 |         if vbytes in PUBLIC:
28 |             raise Exception("Can't do private derivation on public key!")
29 |         I = hmac.new(chaincode, b'\x00' + priv[:32] + encode(i, 256, 4),
30 |                      hashlib.sha512).digest()
31 |     else:
32 |         I = hmac.new(chaincode, pub + encode(i, 256, 4),
33 |                      hashlib.sha512).digest()
34 | 
35 |     if vbytes in PRIVATE:
36 |         newkey = add_privkeys(I[:32] + B'\x01', priv, False)
37 |         fingerprint = bin_hash160(privtopub(key, False))[:4]
38 |     if vbytes in PUBLIC:
39 |         newkey = add_pubkeys([privtopub(I[:32] + '\x01', False), key], False)
40 |         fingerprint = bin_hash160(key)[:4]
41 | 
42 |     return (vbytes, depth + 1, fingerprint, i, I[32:], newkey)
43 | 
44 | def bip32_serialize(rawtuple):
45 |     vbytes, depth, fingerprint, i, chaincode, key = rawtuple
46 |     i = encode(i, 256, 4)
47 |     chaincode = encode(hash_to_int(chaincode), 256, 32)
48 |     keydata = b'\x00' + key[:-1] if vbytes in PRIVATE else key
49 |     bindata = vbytes + from_int_to_byte(
50 |         depth % 256) + fingerprint + i + chaincode + keydata
51 |     return changebase(bindata + bin_dbl_sha256(bindata)[:4], 256, 58)
52 | 
53 | def bip32_deserialize(data):
54 |     dbin = changebase(data, 58, 256)
55 |     if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]:
56 |         raise Exception("Invalid checksum")
57 |     vbytes = dbin[0:4]
58 |     depth = from_byte_to_int(dbin[4])
59 |     fingerprint = dbin[5:9]
60 |     i = decode(dbin[9:13], 256)
61 |     chaincode = dbin[13:45]
62 |     key = dbin[46:78] + b'\x01' if vbytes in PRIVATE else dbin[45:78]
63 |     return (vbytes, depth, fingerprint, i, chaincode, key)
64 | 
65 | def raw_bip32_privtopub(rawtuple):
66 |     vbytes, depth, fingerprint, i, chaincode, key = rawtuple
67 |     newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC
68 |     return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key, False))
69 | 
70 | def bip32_privtopub(data):
71 |     return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data)))
72 | 
73 | def bip32_ckd(data, i):
74 |     return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i))
75 | 
76 | def bip32_master_key(seed, vbytes=MAINNET_PRIVATE):
77 |     I = hmac.new(
78 |         from_string_to_bytes("Bitcoin seed"), seed, hashlib.sha512).digest()
79 |     return bip32_serialize((vbytes, 0, b'\x00' * 4, 0, I[32:], I[:32] + b'\x01'
80 |                            ))
81 | 
82 | def bip32_extract_key(data):
83 |     return safe_hexlify(bip32_deserialize(data)[-1])
84 | 
85 | def bip32_descend(*args):
86 |     if len(args) == 2:
87 |         key, path = args
88 |     else:
89 |         key, path = args[0], map(int, args[1:])
90 |     for p in path:
91 |         key = bip32_ckd(key, p)
92 |     return bip32_extract_key(key)
93 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/secp256k1_main.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/python
  2 | from .py2specials import *
  3 | from .py3specials import *
  4 | import binascii
  5 | import hashlib
  6 | import re
  7 | import sys
  8 | import os
  9 | import base64
 10 | import time
 11 | import random
 12 | import hmac
 13 | import secp256k1
 14 | 
 15 | 
 16 | def privkey_to_address(priv, from_hex=True, magicbyte=0):
 17 |     return pubkey_to_address(privkey_to_pubkey(priv, from_hex), magicbyte)
 18 | 
 19 | privtoaddr = privkey_to_address
 20 | 
 21 | # Hashes
 22 | def bin_hash160(string):
 23 |     intermed = hashlib.sha256(string).digest()
 24 |     return hashlib.new('ripemd160', intermed).digest()
 25 | 
 26 | def hash160(string):
 27 |     return safe_hexlify(bin_hash160(string))
 28 | 
 29 | def bin_sha256(string):
 30 |     binary_data = string if isinstance(string, bytes) else bytes(string,
 31 |                                                                  'utf-8')
 32 |     return hashlib.sha256(binary_data).digest()
 33 | 
 34 | def sha256(string):
 35 |     return bytes_to_hex_string(bin_sha256(string))
 36 | 
 37 | def bin_dbl_sha256(s):
 38 |     bytes_to_hash = from_string_to_bytes(s)
 39 |     return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest()
 40 | 
 41 | def dbl_sha256(string):
 42 |     return safe_hexlify(bin_dbl_sha256(string))
 43 | 
 44 | def hash_to_int(x):
 45 |     if len(x) in [40, 64]:
 46 |         return decode(x, 16)
 47 |     return decode(x, 256)
 48 | 
 49 | def num_to_var_int(x):
 50 |     x = int(x)
 51 |     if x < 253: return from_int_to_byte(x)
 52 |     elif x < 65536: return from_int_to_byte(253) + encode(x, 256, 2)[::-1]
 53 |     elif x < 4294967296: return from_int_to_byte(254) + encode(x, 256, 4)[::-1]
 54 |     else: return from_int_to_byte(255) + encode(x, 256, 8)[::-1]
 55 | 
 56 | # WTF, Electrum?
 57 | def electrum_sig_hash(message):
 58 |     padded = b"\x18Bitcoin Signed Message:\n" + num_to_var_int(len(
 59 |         message)) + from_string_to_bytes(message)
 60 |     return bin_dbl_sha256(padded)
 61 | 
 62 | # Encodings
 63 | def b58check_to_bin(inp):
 64 |     leadingzbytes = len(re.match('^1*', inp).group(0))
 65 |     data = b'\x00' * leadingzbytes + changebase(inp, 58, 256)
 66 |     assert bin_dbl_sha256(data[:-4])[:4] == data[-4:]
 67 |     return data[1:-4]
 68 | 
 69 | def get_version_byte(inp):
 70 |     leadingzbytes = len(re.match('^1*', inp).group(0))
 71 |     data = b'\x00' * leadingzbytes + changebase(inp, 58, 256)
 72 |     assert bin_dbl_sha256(data[:-4])[:4] == data[-4:]
 73 |     return ord(data[0])
 74 | 
 75 | def hex_to_b58check(inp, magicbyte=0):
 76 |     return bin_to_b58check(binascii.unhexlify(inp), magicbyte)
 77 | 
 78 | def b58check_to_hex(inp):
 79 |     return safe_hexlify(b58check_to_bin(inp))
 80 | 
 81 | def pubkey_to_address(pubkey, magicbyte=0):
 82 |     if len(pubkey) in [66, 130]:
 83 |         return bin_to_b58check(
 84 |             bin_hash160(binascii.unhexlify(pubkey)), magicbyte)
 85 |     return bin_to_b58check(bin_hash160(pubkey), magicbyte)
 86 | 
 87 | pubtoaddr = pubkey_to_address
 88 | 
 89 | def wif_compressed_privkey(priv, vbyte=0):
 90 |     """Convert privkey in hex compressed to WIF compressed
 91 |     """
 92 |     if len(priv) != 66:
 93 |         raise Exception("Wrong length of compressed private key")
 94 |     if priv[-2:] != '01':
 95 |         raise Exception("Private key has wrong compression byte")
 96 |     return bin_to_b58check(binascii.unhexlify(priv), 128 + int(vbyte))
 97 | 
 98 | 
 99 | def from_wif_privkey(wif_priv, compressed=True, vbyte=0):
100 |     """Convert WIF compressed privkey to hex compressed.
101 |     Caller specifies the network version byte (0 for mainnet, 0x6f
102 |     for testnet) that the key should correspond to; if there is
103 |     a mismatch an error is thrown. WIF encoding uses 128+ this number.
104 |     """
105 |     bin_key = b58check_to_bin(wif_priv)
106 |     claimed_version_byte = get_version_byte(wif_priv)
107 |     if not 128+vbyte == claimed_version_byte:
108 |         raise Exception(
109 |             "WIF key version byte is wrong network (mainnet/testnet?)")
110 |     if compressed and not len(bin_key) == 33:
111 |         raise Exception("Compressed private key is not 33 bytes")
112 |     if compressed and not bin_key[-1] == '\x01':
113 |         raise Exception("Private key has incorrect compression byte")
114 |     return safe_hexlify(bin_key)
115 | 
116 | def ecdsa_sign(msg, priv, usehex=True):
117 |     #Compatibility issue: old bots will be confused
118 |     #by different msg hashing algo; need to keep electrum_sig_hash, temporarily.
119 |     hashed_msg = electrum_sig_hash(msg)
120 |     if usehex:
121 |         #arguments to raw sign must be consistently hex or bin
122 |         hashed_msg = binascii.hexlify(hashed_msg)
123 |     dersig = ecdsa_raw_sign(hashed_msg, priv, usehex, rawmsg=True)
124 |     #see comments to legacy* functions
125 |     #also, note those functions only handles binary, not hex
126 |     if usehex:
127 |         dersig = binascii.unhexlify(dersig)
128 |     sig = legacy_ecdsa_sign_convert(dersig)
129 |     return base64.b64encode(sig)
130 | 
131 | def ecdsa_verify(msg, sig, pub, usehex=True):
132 |     #See note to ecdsa_sign
133 |     hashed_msg = electrum_sig_hash(msg)
134 |     sig = base64.b64decode(sig)
135 |     #see comments to legacy* functions
136 |     sig = legacy_ecdsa_verify_convert(sig)
137 |     if usehex:
138 |         #arguments to raw_verify must be consistently hex or bin
139 |         hashed_msg = binascii.hexlify(hashed_msg)
140 |         sig = binascii.hexlify(sig)
141 |     return ecdsa_raw_verify(hashed_msg, pub, sig, usehex, rawmsg=True)
142 | 
143 | #A sadly necessary hack until all joinmarket bots are running secp256k1 code.
144 | #pybitcointools *message* signatures (not transaction signatures) used an old signature
145 | #format, basically: [27+y%2] || 32 byte r || 32 byte s,
146 | #instead of DER. These two functions translate the new version into the old so that
147 | #counterparty bots can verify successfully.
148 | def legacy_ecdsa_sign_convert(dersig):
149 |     #note there is no sanity checking of DER format (e.g. leading length byte)
150 |     dersig = dersig[2:]  #e.g. 3045
151 |     rlen = ord(dersig[1])  #ignore leading 02
152 |     #length of r and s: ALWAYS <=33, USUALLY >=32 but can be shorter
153 |     if rlen > 33:
154 |         raise Exception("Incorrectly formatted DER sig:" + binascii.hexlify(
155 |             dersig))
156 |     if dersig[2] == '\x00':
157 |         r = dersig[3:2 + rlen]
158 |         ssig = dersig[2 + rlen:]
159 |     else:
160 |         r = dersig[2:2 + rlen]
161 |         ssig = dersig[2 + rlen:]
162 | 
163 |     slen = ord(ssig[1])  #ignore leading 02
164 |     if slen > 33:
165 |         raise Exception("Incorrectly formatted DER sig:" + binascii.hexlify(
166 |             dersig))
167 |     if len(ssig) != 2 + slen:
168 |         raise Exception("Incorrectly formatted DER sig:" + binascii.hexlify(
169 |             dersig))
170 |     if ssig[2] == '\x00':
171 |         s = ssig[3:2 + slen]
172 |     else:
173 |         s = ssig[2:2 + slen]
174 | 
175 |         #the legacy version requires padding of r and s to 32 bytes with leading zeros
176 |     r = '\x00' * (32 - len(r)) + r
177 |     s = '\x00' * (32 - len(s)) + s
178 | 
179 |     #note: in the original pybitcointools implementation,
180 |     #verification ignored the leading byte (it's only needed for pubkey recovery)
181 |     #so we just ignore parity here.
182 |     return chr(27) + r + s
183 | 
184 | def legacy_ecdsa_verify_convert(sig):
185 |     sig = sig[1:]  #ignore parity byte
186 |     r, s = sig[:32], sig[32:]
187 |     if not len(s) == 32:
188 |         #signature is invalid.
189 |         return False
190 |     #legacy code can produce high S. Need to reintroduce N ::cry::
191 |     N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
192 |     s_int = decode(s, 256)
193 |     # note // is integer division operator in both 2.7 and 3
194 |     s_int = N - s_int if s_int > N // 2 else s_int  #enforce low S.
195 | 
196 |     #on re-encoding, don't use the minlen parameter, because
197 |     #DER does not used fixed (32 byte) length values, so we
198 |     #don't prepend zero bytes to shorter numbers.
199 |     s = encode(s_int, 256)
200 | 
201 |     #as above, remove any front zero padding from r.
202 |     r = encode(decode(r, 256), 256)
203 | 
204 |     #canonicalize r and s
205 |     r, s = ['\x00' + x if ord(x[0]) > 127 else x for x in [r, s]]
206 |     rlen = chr(len(r))
207 |     slen = chr(len(s))
208 |     total_len = 2 + len(r) + 2 + len(s)
209 |     return '\x30' + chr(total_len) + '\x02' + rlen + r + '\x02' + slen + s
210 | 
211 | #Use secp256k1 to handle all EC and ECDSA operations.
212 | #Data types: only hex and binary.
213 | #Compressed and uncompressed private and public keys.
214 | def hexbin(func):
215 |     '''To enable each function to 'speak' either hex or binary,
216 |     requires that the decorated function's final positional argument
217 |     is a boolean flag, True for hex and False for binary.
218 |     '''
219 | 
220 |     def func_wrapper(*args, **kwargs):
221 |         if args[-1]:
222 |             newargs = []
223 |             for arg in args[:-1]:
224 |                 if isinstance(arg, (list, tuple)):
225 |                     newargs += [[x.decode('hex') for x in arg]]
226 |                 else:
227 |                     newargs += [arg.decode('hex')]
228 |             newargs += [False]
229 |             returnval = func(*newargs, **kwargs)
230 |             if isinstance(returnval, bool):
231 |                 return returnval
232 |             else:
233 |                 return binascii.hexlify(returnval)
234 |         else:
235 |             return func(*args, **kwargs)
236 | 
237 |     return func_wrapper
238 | 
239 | def read_privkey(priv):
240 |     if len(priv) == 33:
241 |         if priv[-1] == '\x01':
242 |             compressed = True
243 |         else:
244 |             raise Exception("Invalid private key")
245 |     elif len(priv) == 32:
246 |         compressed = False
247 |     else:
248 |         raise Exception("Invalid private key")
249 |     return (compressed, priv[:32])
250 | 
251 | @hexbin
252 | def privkey_to_pubkey_inner(priv, usehex):
253 |     '''Take 32/33 byte raw private key as input.
254 |     If 32 bytes, return compressed (33 byte) raw public key.
255 |     If 33 bytes, read the final byte as compression flag,
256 |     and return compressed/uncompressed public key as appropriate.'''
257 |     compressed, priv = read_privkey(priv)
258 |     #secp256k1 checks for validity of key value.
259 |     newpriv = secp256k1.PrivateKey(privkey=priv)
260 |     return newpriv.pubkey.serialize(compressed=compressed)
261 | 
262 | def privkey_to_pubkey(priv, usehex=True):
263 |     '''To avoid changing the interface from the legacy system,
264 |     allow an *optional* hex argument here (called differently from
265 |     maker/taker code to how it's called in bip32 code), then
266 |     pass to the standard hexbin decorator under the hood.
267 |     '''
268 |     return privkey_to_pubkey_inner(priv, usehex)
269 | 
270 | privtopub = privkey_to_pubkey
271 | 
272 | @hexbin
273 | def multiply(s, pub, usehex, rawpub=True):
274 |     '''Input binary compressed pubkey P(33 bytes)
275 |     and scalar s(32 bytes), return s*P.
276 |     The return value is a binary compressed public key.
277 |     Note that the called function does the type checking
278 |     of the scalar s.
279 |     ('raw' options passed in)
280 |     '''
281 |     newpub = secp256k1.PublicKey(pub, raw=rawpub)
282 |     res = newpub.tweak_mul(s)
283 |     return res.serialize()
284 | 
285 | @hexbin
286 | def add_pubkeys(pubkeys, usehex):
287 |     '''Input a list of binary compressed pubkeys
288 |     and return their sum as a binary compressed pubkey.'''
289 |     r = secp256k1.PublicKey()  #dummy holding object
290 |     pubkey_list = [secp256k1.PublicKey(x,
291 |                                        raw=True).public_key for x in pubkeys]
292 |     r.combine(pubkey_list)
293 |     return r.serialize()
294 | 
295 | @hexbin
296 | def add_privkeys(priv1, priv2, usehex):
297 |     '''Add privkey 1 to privkey 2.
298 |     Input keys must be in binary either compressed or not.
299 |     Returned key will have the same compression state.
300 |     Error if compression state of both input keys is not the same.'''
301 |     y, z = [read_privkey(x) for x in [priv1, priv2]]
302 |     if y[0] != z[0]:
303 |         raise Exception("cannot add privkeys, mixed compression formats")
304 |     else:
305 |         compressed = y[0]
306 |     newpriv1, newpriv2 = (y[1], z[1])
307 |     p1 = secp256k1.PrivateKey(newpriv1, raw=True)
308 |     res = p1.tweak_add(newpriv2)
309 |     if compressed:
310 |         res += '\x01'
311 |     return res
312 | 
313 | @hexbin
314 | def ecdsa_raw_sign(msg,
315 |                    priv,
316 |                    usehex,
317 |                    rawpriv=True,
318 |                    rawmsg=False,
319 |                    usenonce=None):
320 |     '''Take the binary message msg and sign it with the private key
321 |     priv.
322 |     By default priv is just a 32 byte string, if rawpriv is false
323 |     it is assumed to be DER encoded.
324 |     If rawmsg is True, no sha256 hash is applied to msg before signing.
325 |     In this case, msg must be a precalculated hash (256 bit).
326 |     If rawmsg is False, the secp256k1 lib will hash the message as part
327 |     of the ECDSA-SHA256 signing algo.
328 |     If usenonce is not None, its value is passed to the secp256k1 library
329 |     sign() function as the ndata value, which is then used in conjunction
330 |     with a custom nonce generating function, such that the nonce used in the ECDSA
331 |     sign algorithm is exactly that value (ndata there, usenonce here). 32 bytes.
332 |     Return value: the calculated signature.'''
333 |     if rawmsg and len(msg) != 32:
334 |         raise Exception("Invalid hash input to ECDSA raw sign.")
335 |     if rawpriv:
336 |         compressed, p = read_privkey(priv)
337 |         newpriv = secp256k1.PrivateKey(p, raw=True)
338 |     else:
339 |         newpriv = secp256k1.PrivateKey(priv, raw=False)
340 |     if usenonce and len(usenonce) != 32:
341 |         raise ValueError("Invalid nonce passed to ecdsa_sign: " + str(usenonce))
342 | 
343 |     sig = newpriv.ecdsa_sign(msg, raw=rawmsg)
344 |     return newpriv.ecdsa_serialize(sig)
345 | 
346 | @hexbin
347 | def ecdsa_raw_verify(msg, pub, sig, usehex, rawmsg=False):
348 |     '''Take the binary message msg and binary signature sig,
349 |     and verify it against the pubkey pub.
350 |     If rawmsg is True, no sha256 hash is applied to msg before verifying.
351 |     In this case, msg must be a precalculated hash (256 bit).
352 |     If rawmsg is False, the secp256k1 lib will hash the message as part
353 |     of the ECDSA-SHA256 verification algo.
354 |     Return value: True if the signature is valid for this pubkey, False
355 |     otherwise. '''
356 |     if rawmsg and len(msg) != 32:
357 |         raise Exception("Invalid hash input to ECDSA raw sign.")
358 |     newpub = secp256k1.PublicKey(pubkey=pub, raw=True)
359 |     sigobj = newpub.ecdsa_deserialize(sig)
360 |     return newpub.ecdsa_verify(msg, sigobj, raw=rawmsg)
361 | 
362 | def estimate_tx_size(ins, outs, txtype='p2pkh'):
363 |     '''Estimate transaction size.
364 |     Assuming p2pkh:
365 |     out: 8+1+3+2+20=34, in: 1+32+4+1+1+~73+1+1+33=147,
366 |     ver:4,seq:4, +2 (len in,out)
367 |     total ~= 34*len_out + 147*len_in + 10 (sig sizes vary slightly)
368 |     '''
369 |     if txtype == 'p2pkh':
370 |         return 10 + ins * 147 + 34 * outs
371 |     else:
372 |         raise NotImplementedError("Non p2pkh transaction size estimation not" +
373 |                                   "yet implemented")
374 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/secp256k1_transaction.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/python
  2 | import binascii, re, json, copy, sys
  3 | from electrumpersonalserver.bitcoin.secp256k1_main import *
  4 | from _functools import reduce
  5 | import os
  6 | 
  7 | is_python2 = sys.version_info.major == 2
  8 | 
  9 | ### Hex to bin converter and vice versa for objects
 10 | def json_is_base(obj, base):
 11 |     if not is_python2 and isinstance(obj, bytes):
 12 |         return False
 13 | 
 14 |     alpha = get_code_string(base)
 15 |     if isinstance(obj, string_types):
 16 |         for i in range(len(obj)):
 17 |             if alpha.find(obj[i]) == -1:
 18 |                 return False
 19 |         return True
 20 |     elif isinstance(obj, int_types) or obj is None:
 21 |         return True
 22 |     elif isinstance(obj, list):
 23 |         for i in range(len(obj)):
 24 |             if not json_is_base(obj[i], base):
 25 |                 return False
 26 |         return True
 27 |     else:
 28 |         for x in obj:
 29 |             if not json_is_base(obj[x], base):
 30 |                 return False
 31 |         return True
 32 | 
 33 | 
 34 | def json_changebase(obj, changer):
 35 |     if isinstance(obj, string_or_bytes_types):
 36 |         return changer(obj)
 37 |     elif isinstance(obj, int_types) or obj is None:
 38 |         return obj
 39 |     elif isinstance(obj, list):
 40 |         return [json_changebase(x, changer) for x in obj]
 41 |     return dict((x, json_changebase(obj[x], changer)) for x in obj)
 42 | 
 43 | # Transaction serialization and deserialization
 44 | 
 45 | 
 46 | def deserialize(tx):
 47 |     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*
#39;, tx):
 48 |         #tx = bytes(bytearray.fromhex(tx))
 49 |         return json_changebase(
 50 |             deserialize(binascii.unhexlify(tx)), lambda x: safe_hexlify(x))
 51 |     # http://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope
 52 |     # Python's scoping rules are demented, requiring me to make pos an object
 53 |     # so that it is call-by-reference
 54 |     pos = [0]
 55 | 
 56 |     def read_as_int(bytez):
 57 |         pos[0] += bytez
 58 |         return decode(tx[pos[0] - bytez:pos[0]][::-1], 256)
 59 | 
 60 |     def read_var_int():
 61 |         pos[0] += 1
 62 | 
 63 |         val = from_byte_to_int(tx[pos[0] - 1])
 64 |         if val < 253:
 65 |             return val
 66 |         return read_as_int(pow(2, val - 252))
 67 | 
 68 |     def read_bytes(bytez):
 69 |         pos[0] += bytez
 70 |         return tx[pos[0] - bytez:pos[0]]
 71 | 
 72 |     def read_var_string():
 73 |         size = read_var_int()
 74 |         return read_bytes(size)
 75 | 
 76 |     obj = {"ins": [], "outs": []}
 77 |     obj["version"] = read_as_int(4)
 78 |     ins = read_var_int()
 79 |     for i in range(ins):
 80 |         obj["ins"].append({
 81 |             "outpoint": {
 82 |                 "hash": read_bytes(32)[::-1],
 83 |                 "index": read_as_int(4)
 84 |             },
 85 |             "script": read_var_string(),
 86 |             "sequence": read_as_int(4)
 87 |         })
 88 |     outs = read_var_int()
 89 |     for i in range(outs):
 90 |         obj["outs"].append({
 91 |             "value": read_as_int(8),
 92 |             "script": read_var_string()
 93 |         })
 94 |     obj["locktime"] = read_as_int(4)
 95 |     return obj
 96 | 
 97 | 
 98 | def serialize(txobj):
 99 |     #if isinstance(txobj, bytes):
100 |     #    txobj = bytes_to_hex_string(txobj)
101 |     o = []
102 |     if json_is_base(txobj, 16):
103 |         json_changedbase = json_changebase(txobj,
104 |                                            lambda x: binascii.unhexlify(x))
105 |         hexlified = safe_hexlify(serialize(json_changedbase))
106 |         return hexlified
107 |     o.append(encode(txobj["version"], 256, 4)[::-1])
108 |     o.append(num_to_var_int(len(txobj["ins"])))
109 |     for inp in txobj["ins"]:
110 |         o.append(inp["outpoint"]["hash"][::-1])
111 |         o.append(encode(inp["outpoint"]["index"], 256, 4)[::-1])
112 |         o.append(num_to_var_int(len(inp["script"])) + (inp["script"] if inp[
113 |             "script"] or is_python2 else bytes()))
114 |         o.append(encode(inp["sequence"], 256, 4)[::-1])
115 |     o.append(num_to_var_int(len(txobj["outs"])))
116 |     for out in txobj["outs"]:
117 |         o.append(encode(out["value"], 256, 8)[::-1])
118 |         o.append(num_to_var_int(len(out["script"])) + out["script"])
119 |     o.append(encode(txobj["locktime"], 256, 4)[::-1])
120 | 
121 |     return ''.join(o) if is_python2 else reduce(lambda x, y: x + y, o, bytes())
122 | 
123 | # Hashing transactions for signing
124 | 
125 | SIGHASH_ALL = 1
126 | SIGHASH_NONE = 2
127 | SIGHASH_SINGLE = 3
128 | SIGHASH_ANYONECANPAY = 0x80
129 | 
130 | def signature_form(tx, i, script, hashcode=SIGHASH_ALL):
131 |     i, hashcode = int(i), int(hashcode)
132 |     if isinstance(tx, string_or_bytes_types):
133 |         return serialize(signature_form(deserialize(tx), i, script, hashcode))
134 |     newtx = copy.deepcopy(tx)
135 |     for inp in newtx["ins"]:
136 |         inp["script"] = ""
137 |     newtx["ins"][i]["script"] = script
138 |     if hashcode & 0x1f == SIGHASH_NONE:
139 |         newtx["outs"] = []
140 |         for j, inp in enumerate(newtx["ins"]):
141 |             if j != i:
142 |                 inp["sequence"] = 0
143 |     elif hashcode & 0x1f == SIGHASH_SINGLE:
144 |         if len(newtx["ins"]) > len(newtx["outs"]):
145 |             raise Exception(
146 |                 "Transactions with sighash single should have len in <= len out")
147 |         newtx["outs"] = newtx["outs"][:i+1]
148 |         for out in newtx["outs"][:i]:
149 |             out['value'] = 2**64 - 1
150 |             out['script'] = ""
151 |         for j, inp in enumerate(newtx["ins"]):
152 |             if j != i:
153 |                 inp["sequence"] = 0
154 |     if hashcode & SIGHASH_ANYONECANPAY:
155 |         newtx["ins"] = [newtx["ins"][i]]
156 |     else:
157 |         pass
158 |     return newtx
159 | 
160 | def txhash(tx, hashcode=None):
161 |     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*
#39;, tx):
162 |         tx = changebase(tx, 16, 256)
163 |     if hashcode:
164 |         return dbl_sha256(from_string_to_bytes(tx) + encode(
165 |             int(hashcode), 256, 4)[::-1])
166 |     else:
167 |         return safe_hexlify(bin_dbl_sha256(tx)[::-1])
168 | 
169 | 
170 | def bin_txhash(tx, hashcode=None):
171 |     return binascii.unhexlify(txhash(tx, hashcode))
172 | 
173 | 
174 | def ecdsa_tx_sign(tx, priv, hashcode=SIGHASH_ALL, usenonce=None):
175 |     sig = ecdsa_raw_sign(
176 |         txhash(tx, hashcode),
177 |         priv,
178 |         True,
179 |         rawmsg=True,
180 |         usenonce=usenonce)
181 |     return sig + encode(hashcode, 16, 2)
182 | 
183 | 
184 | def ecdsa_tx_verify(tx, sig, pub, hashcode=SIGHASH_ALL):
185 |     return ecdsa_raw_verify(
186 |         txhash(tx, hashcode),
187 |         pub,
188 |         sig[:-2],
189 |         True,
190 |         rawmsg=True)
191 | 
192 | # Scripts
193 | 
194 | 
195 | def mk_pubkey_script(addr):
196 |     # Keep the auxiliary functions around for altcoins' sake
197 |     return '76a914' + b58check_to_hex(addr) + '88ac'
198 | 
199 | 
200 | def mk_scripthash_script(addr):
201 |     return 'a914' + b58check_to_hex(addr) + '87'
202 | 
203 | # Address representation to output script
204 | 
205 | 
206 | def address_to_script(addr):
207 |     if addr[0] == '3' or addr[0] == '2':
208 |         return mk_scripthash_script(addr)
209 |     else:
210 |         return mk_pubkey_script(addr)
211 | 
212 | # Output script to address representation
213 | 
214 | 
215 | def script_to_address(script, vbyte=0):
216 |     if re.match('^[0-9a-fA-F]*
#39;, script):
217 |         script = binascii.unhexlify(script)
218 |     if script[:3] == b'\x76\xa9\x14' and script[-2:] == b'\x88\xac' and len(
219 |             script) == 25:
220 |         return bin_to_b58check(script[3:-2], vbyte)  # pubkey hash addresses
221 |     else:
222 |         if vbyte in [111, 196]:
223 |             # Testnet
224 |             scripthash_byte = 196
225 |         else:
226 |             scripthash_byte = 5
227 |         # BIP0016 scripthash addresses
228 |         return bin_to_b58check(script[2:-1], scripthash_byte)
229 | 
230 | 
231 | def p2sh_scriptaddr(script, magicbyte=5):
232 |     if re.match('^[0-9a-fA-F]*
#39;, script):
233 |         script = binascii.unhexlify(script)
234 |     return hex_to_b58check(hash160(script), magicbyte)
235 | 
236 | 
237 | scriptaddr = p2sh_scriptaddr
238 | 
239 | 
240 | def deserialize_script(script):
241 |     if isinstance(script, str) and re.match('^[0-9a-fA-F]*
#39;, script):
242 |         return json_changebase(
243 |             deserialize_script(binascii.unhexlify(script)),
244 |             lambda x: safe_hexlify(x))
245 |     out, pos = [], 0
246 |     while pos < len(script):
247 |         code = from_byte_to_int(script[pos])
248 |         if code == 0:
249 |             out.append(None)
250 |             pos += 1
251 |         elif code <= 75:
252 |             out.append(script[pos + 1:pos + 1 + code])
253 |             pos += 1 + code
254 |         elif code <= 78:
255 |             szsz = pow(2, code - 76)
256 |             sz = decode(script[pos + szsz:pos:-1], 256)
257 |             out.append(script[pos + 1 + szsz:pos + 1 + szsz + sz])
258 |             pos += 1 + szsz + sz
259 |         elif code <= 96:
260 |             out.append(code - 80)
261 |             pos += 1
262 |         else:
263 |             out.append(code)
264 |             pos += 1
265 |     return out
266 | 
267 | 
268 | def serialize_script_unit(unit):
269 |     if isinstance(unit, int):
270 |         if unit < 16:
271 |             return from_int_to_byte(unit + 80)
272 |         else:
273 |             return bytes([unit])
274 |     elif unit is None:
275 |         return b'\x00'
276 |     else:
277 |         if len(unit) <= 75:
278 |             return from_int_to_byte(len(unit)) + unit
279 |         elif len(unit) < 256:
280 |             return from_int_to_byte(76) + from_int_to_byte(len(unit)) + unit
281 |         elif len(unit) < 65536:
282 |             return from_int_to_byte(77) + encode(len(unit), 256, 2)[::-1] + unit
283 |         else:
284 |             return from_int_to_byte(78) + encode(len(unit), 256, 4)[::-1] + unit
285 | 
286 | 
287 | if is_python2:
288 | 
289 |     def serialize_script(script):
290 |         if json_is_base(script, 16):
291 |             return binascii.hexlify(serialize_script(json_changebase(
292 |                 script, lambda x: binascii.unhexlify(x))))
293 |         return ''.join(map(serialize_script_unit, script))
294 | else:
295 | 
296 |     def serialize_script(script):
297 |         if json_is_base(script, 16):
298 |             return safe_hexlify(serialize_script(json_changebase(
299 |                 script, lambda x: binascii.unhexlify(x))))
300 | 
301 |         result = bytes()
302 |         for b in map(serialize_script_unit, script):
303 |             result += b if isinstance(b, bytes) else bytes(b, 'utf-8')
304 |         return result
305 | 
306 | 
307 | def mk_multisig_script(*args):  # [pubs],k or pub1,pub2...pub[n],k
308 |     if isinstance(args[0], list):
309 |         pubs, k = args[0], int(args[1])
310 |     else:
311 |         pubs = list(filter(lambda x: len(str(x)) >= 32, args))
312 |         k = int(args[len(pubs)])
313 |     return serialize_script([k] + pubs + [len(pubs)]) + 'ae'
314 | 
315 | # Signing and verifying
316 | 
317 | 
318 | def verify_tx_input(tx, i, script, sig, pub):
319 |     if re.match('^[0-9a-fA-F]*
#39;, tx):
320 |         tx = binascii.unhexlify(tx)
321 |     if re.match('^[0-9a-fA-F]*
#39;, script):
322 |         script = binascii.unhexlify(script)
323 |     if not re.match('^[0-9a-fA-F]*
#39;, sig):
324 |         sig = safe_hexlify(sig)
325 |     if not re.match('^[0-9a-fA-F]*
#39;, pub):
326 |         pub = safe_hexlify(pub)
327 |     hashcode = decode(sig[-2:], 16)
328 |     modtx = signature_form(tx, int(i), script, hashcode)
329 |     return ecdsa_tx_verify(modtx, sig, pub, hashcode)
330 | 
331 | 
332 | def sign(tx, i, priv, hashcode=SIGHASH_ALL, usenonce=None):
333 |     i = int(i)
334 |     if (not is_python2 and isinstance(re, bytes)) or not re.match(
335 |             '^[0-9a-fA-F]*
#39;, tx):
336 |         return binascii.unhexlify(sign(safe_hexlify(tx), i, priv))
337 |     if len(priv) <= 33:
338 |         priv = safe_hexlify(priv)
339 |     pub = privkey_to_pubkey(priv, True)
340 |     address = pubkey_to_address(pub)
341 |     signing_tx = signature_form(tx, i, mk_pubkey_script(address), hashcode)
342 |     sig = ecdsa_tx_sign(signing_tx, priv, hashcode, usenonce=usenonce)
343 |     txobj = deserialize(tx)
344 |     txobj["ins"][i]["script"] = serialize_script([sig, pub])
345 |     return serialize(txobj)
346 | 
347 | 
348 | def signall(tx, priv):
349 |     # if priv is a dictionary, assume format is
350 |     # { 'txinhash:txinidx' : privkey }
351 |     if isinstance(priv, dict):
352 |         for e, i in enumerate(deserialize(tx)["ins"]):
353 |             k = priv["%s:%d" % (i["outpoint"]["hash"], i["outpoint"]["index"])]
354 |             tx = sign(tx, e, k)
355 |     else:
356 |         for i in range(len(deserialize(tx)["ins"])):
357 |             tx = sign(tx, i, priv)
358 |     return tx
359 | 
360 | 
361 | def multisign(tx, i, script, pk, hashcode=SIGHASH_ALL):
362 |     if re.match('^[0-9a-fA-F]*
#39;, tx):
363 |         tx = binascii.unhexlify(tx)
364 |     if re.match('^[0-9a-fA-F]*
#39;, script):
365 |         script = binascii.unhexlify(script)
366 |     modtx = signature_form(tx, i, script, hashcode)
367 |     return ecdsa_tx_sign(modtx, pk, hashcode)
368 | 
369 | 
370 | def apply_multisignatures(*args):
371 |     # tx,i,script,sigs OR tx,i,script,sig1,sig2...,sig[n]
372 |     tx, i, script = args[0], int(args[1]), args[2]
373 |     sigs = args[3] if isinstance(args[3], list) else list(args[3:])
374 | 
375 |     if isinstance(script, str) and re.match('^[0-9a-fA-F]*
#39;, script):
376 |         script = binascii.unhexlify(script)
377 |     sigs = [binascii.unhexlify(x) if x[:2] == '30' else x for x in sigs]
378 |     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*
#39;, tx):
379 |         return safe_hexlify(apply_multisignatures(
380 |             binascii.unhexlify(tx), i, script, sigs))
381 | 
382 |     txobj = deserialize(tx)
383 |     txobj["ins"][i]["script"] = serialize_script([None] + sigs + [script])
384 |     return serialize(txobj)
385 | 
386 | 
387 | def is_inp(arg):
388 |     return len(arg) > 64 or "output" in arg or "outpoint" in arg
389 | 
390 | 
391 | def mktx(*args):
392 |     # [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ...
393 |     ins, outs = [], []
394 |     for arg in args:
395 |         if isinstance(arg, list):
396 |             for a in arg:
397 |                 (ins if is_inp(a) else outs).append(a)
398 |         else:
399 |             (ins if is_inp(arg) else outs).append(arg)
400 | 
401 |     txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []}
402 |     for i in ins:
403 |         if isinstance(i, dict) and "outpoint" in i:
404 |             txobj["ins"].append(i)
405 |         else:
406 |             if isinstance(i, dict) and "output" in i:
407 |                 i = i["output"]
408 |             txobj["ins"].append({
409 |                 "outpoint": {"hash": i[:64],
410 |                              "index": int(i[65:])},
411 |                 "script": "",
412 |                 "sequence": 4294967295
413 |             })
414 |     for o in outs:
415 |         if isinstance(o, string_or_bytes_types):
416 |             addr = o[:o.find(':')]
417 |             val = int(o[o.find(':') + 1:])
418 |             o = {}
419 |             if re.match('^[0-9a-fA-F]*
#39;, addr):
420 |                 o["script"] = addr
421 |             else:
422 |                 o["address"] = addr
423 |             o["value"] = val
424 | 
425 |         outobj = {}
426 |         if "address" in o:
427 |             outobj["script"] = address_to_script(o["address"])
428 |         elif "script" in o:
429 |             outobj["script"] = o["script"]
430 |         else:
431 |             raise Exception("Could not find 'address' or 'script' in output.")
432 |         outobj["value"] = o["value"]
433 |         txobj["outs"].append(outobj)
434 | 
435 |     return serialize(txobj)
436 | 
437 | 
438 | def select(unspent, value):
439 |     value = int(value)
440 |     high = [u for u in unspent if u["value"] >= value]
441 |     high.sort(key=lambda u: u["value"])
442 |     low = [u for u in unspent if u["value"] < value]
443 |     low.sort(key=lambda u: -u["value"])
444 |     if len(high):
445 |         return [high[0]]
446 |     i, tv = 0, 0
447 |     while tv < value and i < len(low):
448 |         tv += low[i]["value"]
449 |         i += 1
450 |     if tv < value:
451 |         raise Exception("Not enough funds")
452 |     return low[:i]
453 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/bitcoin/transaction.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/python
  2 | import binascii, re, json, copy, sys
  3 | from electrumpersonalserver.bitcoin.main import *
  4 | from _functools import reduce
  5 | 
  6 | ### Hex to bin converter and vice versa for objects
  7 | 
  8 | 
  9 | def json_is_base(obj, base):
 10 |     if not is_python2 and isinstance(obj, bytes):
 11 |         return False
 12 | 
 13 |     alpha = get_code_string(base)
 14 |     if isinstance(obj, string_types):
 15 |         for i in range(len(obj)):
 16 |             if alpha.find(obj[i]) == -1:
 17 |                 return False
 18 |         return True
 19 |     elif isinstance(obj, int_types) or obj is None:
 20 |         return True
 21 |     elif isinstance(obj, list):
 22 |         for i in range(len(obj)):
 23 |             if not json_is_base(obj[i], base):
 24 |                 return False
 25 |         return True
 26 |     else:
 27 |         for x in obj:
 28 |             if not json_is_base(obj[x], base):
 29 |                 return False
 30 |         return True
 31 | 
 32 | 
 33 | def json_changebase(obj, changer):
 34 |     if isinstance(obj, string_or_bytes_types):
 35 |         return changer(obj)
 36 |     elif isinstance(obj, int_types) or obj is None:
 37 |         return obj
 38 |     elif isinstance(obj, list):
 39 |         return [json_changebase(x, changer) for x in obj]
 40 |     return dict((x, json_changebase(obj[x], changer)) for x in obj)
 41 | 
 42 | # Transaction serialization and deserialization
 43 | 
 44 | 
 45 | def deserialize(tx):
 46 |     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*
#39;, tx):
 47 |         #tx = bytes(bytearray.fromhex(tx))
 48 |         return json_changebase(
 49 |             deserialize(binascii.unhexlify(tx)), lambda x: safe_hexlify(x))
 50 |     # http://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope
 51 |     # Python's scoping rules are demented, requiring me to make pos an object
 52 |     # so that it is call-by-reference
 53 |     pos = [0]
 54 | 
 55 |     def read_as_int(bytez):
 56 |         pos[0] += bytez
 57 |         return decode(tx[pos[0] - bytez:pos[0]][::-1], 256)
 58 | 
 59 |     def read_var_int():
 60 |         pos[0] += 1
 61 | 
 62 |         val = from_byte_to_int(tx[pos[0] - 1])
 63 |         if val < 253:
 64 |             return val
 65 |         return read_as_int(pow(2, val - 252))
 66 | 
 67 |     def read_bytes(bytez):
 68 |         pos[0] += bytez
 69 |         return tx[pos[0] - bytez:pos[0]]
 70 | 
 71 |     def read_var_string():
 72 |         size = read_var_int()
 73 |         return read_bytes(size)
 74 | 
 75 |     obj = {"ins": [], "outs": []}
 76 |     obj["version"] = read_as_int(4)
 77 |     ins = read_var_int()
 78 |     for i in range(ins):
 79 |         obj["ins"].append({
 80 |             "outpoint": {
 81 |                 "hash": read_bytes(32)[::-1],
 82 |                 "index": read_as_int(4)
 83 |             },
 84 |             "script": read_var_string(),
 85 |             "sequence": read_as_int(4)
 86 |         })
 87 |     outs = read_var_int()
 88 |     for i in range(outs):
 89 |         obj["outs"].append({
 90 |             "value": read_as_int(8),
 91 |             "script": read_var_string()
 92 |         })
 93 |     obj["locktime"] = read_as_int(4)
 94 |     return obj
 95 | 
 96 | 
 97 | def serialize(txobj):
 98 |     #if isinstance(txobj, bytes):
 99 |     #    txobj = bytes_to_hex_string(txobj)
100 |     o = []
101 |     if json_is_base(txobj, 16):
102 |         json_changedbase = json_changebase(txobj,
103 |                                            lambda x: binascii.unhexlify(x))
104 |         hexlified = safe_hexlify(serialize(json_changedbase))
105 |         return hexlified
106 |     o.append(encode(txobj["version"], 256, 4)[::-1])
107 |     o.append(num_to_var_int(len(txobj["ins"])))
108 |     for inp in txobj["ins"]:
109 |         o.append(inp["outpoint"]["hash"][::-1])
110 |         o.append(encode(inp["outpoint"]["index"], 256, 4)[::-1])
111 |         o.append(num_to_var_int(len(inp["script"])) + (inp["script"] if inp[
112 |             "script"] or is_python2 else bytes()))
113 |         o.append(encode(inp["sequence"], 256, 4)[::-1])
114 |     o.append(num_to_var_int(len(txobj["outs"])))
115 |     for out in txobj["outs"]:
116 |         o.append(encode(out["value"], 256, 8)[::-1])
117 |         o.append(num_to_var_int(len(out["script"])) + out["script"])
118 |     o.append(encode(txobj["locktime"], 256, 4)[::-1])
119 | 
120 |     return ''.join(o) if is_python2 else reduce(lambda x, y: x + y, o, bytes())
121 | 
122 | # Hashing transactions for signing
123 | 
124 | SIGHASH_ALL = 1
125 | SIGHASH_NONE = 2
126 | SIGHASH_SINGLE = 3
127 | # this works like SIGHASH_ANYONECANPAY | SIGHASH_ALL, might as well make it explicit while
128 | # we fix the constant
129 | SIGHASH_ANYONECANPAY = 0x81
130 | 
131 | 
132 | def signature_form(tx, i, script, hashcode=SIGHASH_ALL):
133 |     i, hashcode = int(i), int(hashcode)
134 |     if isinstance(tx, string_or_bytes_types):
135 |         return serialize(signature_form(deserialize(tx), i, script, hashcode))
136 |     newtx = copy.deepcopy(tx)
137 |     for inp in newtx["ins"]:
138 |         inp["script"] = ""
139 |     newtx["ins"][i]["script"] = script
140 |     if hashcode == SIGHASH_NONE:
141 |         newtx["outs"] = []
142 |     elif hashcode == SIGHASH_SINGLE:
143 |         newtx["outs"] = newtx["outs"][:len(newtx["ins"])]
144 |         for out in range(len(newtx["ins"]) - 1):
145 |             out.value = 2**64 - 1
146 |             out.script = ""
147 |     elif hashcode == SIGHASH_ANYONECANPAY:
148 |         newtx["ins"] = [newtx["ins"][i]]
149 |     else:
150 |         pass
151 |     return newtx
152 | 
153 | # Making the actual signatures
154 | 
155 | 
156 | def der_encode_sig(v, r, s):
157 |     """Takes (vbyte, r, s) as ints and returns hex der encode sig"""
158 |     #See https://github.com/vbuterin/pybitcointools/issues/89
159 |     #See https://github.com/simcity4242/pybitcointools/
160 |     s = N - s if s > N // 2 else s  # BIP62 low s
161 |     b1, b2 = encode(r, 256), encode(s, 256)
162 |     if bytearray(b1)[
163 |             0] & 0x80:  # add null bytes if leading byte interpreted as negative
164 |         b1 = b'\x00' + b1
165 |     if bytearray(b2)[0] & 0x80:
166 |         b2 = b'\x00' + b2
167 |     left = b'\x02' + encode(len(b1), 256, 1) + b1
168 |     right = b'\x02' + encode(len(b2), 256, 1) + b2
169 |     return safe_hexlify(b'\x30' + encode(
170 |         len(left + right), 256, 1) + left + right)
171 | 
172 | 
173 | def der_decode_sig(sig):
174 |     leftlen = decode(sig[6:8], 16) * 2
175 |     left = sig[8:8 + leftlen]
176 |     rightlen = decode(sig[10 + leftlen:12 + leftlen], 16) * 2
177 |     right = sig[12 + leftlen:12 + leftlen + rightlen]
178 |     return (None, decode(left, 16), decode(right, 16))
179 | 
180 | 
181 | def txhash(tx, hashcode=None):
182 |     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*
#39;, tx):
183 |         tx = changebase(tx, 16, 256)
184 |     if hashcode:
185 |         return dbl_sha256(from_string_to_bytes(tx) + encode(
186 |             int(hashcode), 256, 4)[::-1])
187 |     else:
188 |         return safe_hexlify(bin_dbl_sha256(tx)[::-1])
189 | 
190 | 
191 | def bin_txhash(tx, hashcode=None):
192 |     return binascii.unhexlify(txhash(tx, hashcode))
193 | 
194 | 
195 | def ecdsa_tx_sign(tx, priv, hashcode=SIGHASH_ALL):
196 |     rawsig = ecdsa_raw_sign(bin_txhash(tx, hashcode), priv)
197 |     return der_encode_sig(*rawsig) + encode(hashcode, 16, 2)
198 | 
199 | 
200 | def ecdsa_tx_verify(tx, sig, pub, hashcode=SIGHASH_ALL):
201 |     return ecdsa_raw_verify(bin_txhash(tx, hashcode), der_decode_sig(sig), pub)
202 | 
203 | # Scripts
204 | 
205 | def mk_pubkey_script(addr):
206 |     # Keep the auxiliary functions around for altcoins' sake
207 |     return '76a914' + b58check_to_hex(addr) + '88ac'
208 | 
209 | 
210 | def mk_scripthash_script(addr):
211 |     return 'a914' + b58check_to_hex(addr) + '87'
212 | 
213 | # Address representation to output script
214 | 
215 | 
216 | def address_to_script(addr):
217 |     if addr[0] == '3' or addr[0] == '2':
218 |         return mk_scripthash_script(addr)
219 |     else:
220 |         return mk_pubkey_script(addr)
221 | 
222 | # Output script to address representation
223 | 
224 | 
225 | def script_to_address(script, vbyte=0):
226 |     if re.match('^[0-9a-fA-F]*
#39;, script):
227 |         script = binascii.unhexlify(script)
228 |     if script[:3] == b'\x76\xa9\x14' and script[-2:] == b'\x88\xac' and len(
229 |             script) == 25:
230 |         return bin_to_b58check(script[3:-2], vbyte)  # pubkey hash addresses
231 |     else:
232 |         if vbyte in [111, 196]:
233 |             # Testnet
234 |             scripthash_byte = 196
235 |         else:
236 |             scripthash_byte = 5
237 |         # BIP0016 scripthash addresses
238 |         return bin_to_b58check(script[2:-1], scripthash_byte)
239 | 
240 | 
241 | def p2sh_scriptaddr(script, magicbyte=5):
242 |     if re.match('^[0-9a-fA-F]*
#39;, script):
243 |         script = binascii.unhexlify(script)
244 |     return hex_to_b58check(hash160(script), magicbyte)
245 | 
246 | 
247 | scriptaddr = p2sh_scriptaddr
248 | 
249 | 
250 | def deserialize_script(script):
251 |     if isinstance(script, str) and re.match('^[0-9a-fA-F]*
#39;, script):
252 |         return json_changebase(
253 |             deserialize_script(binascii.unhexlify(script)),
254 |             lambda x: safe_hexlify(x))
255 |     out, pos = [], 0
256 |     while pos < len(script):
257 |         code = from_byte_to_int(script[pos])
258 |         if code == 0:
259 |             out.append(None)
260 |             pos += 1
261 |         elif code <= 75:
262 |             out.append(script[pos + 1:pos + 1 + code])
263 |             pos += 1 + code
264 |         elif code <= 78:
265 |             szsz = pow(2, code - 76)
266 |             sz = decode(script[pos + szsz:pos:-1], 256)
267 |             out.append(script[pos + 1 + szsz:pos + 1 + szsz + sz])
268 |             pos += 1 + szsz + sz
269 |         elif code <= 96:
270 |             out.append(code - 80)
271 |             pos += 1
272 |         else:
273 |             out.append(code)
274 |             pos += 1
275 |     return out
276 | 
277 | 
278 | def serialize_script_unit(unit):
279 |     if isinstance(unit, int):
280 |         if unit < 16:
281 |             return from_int_to_byte(unit + 80)
282 |         else:
283 |             return bytes([unit])
284 |     elif unit is None:
285 |         return b'\x00'
286 |     else:
287 |         if len(unit) <= 75:
288 |             return from_int_to_byte(len(unit)) + unit
289 |         elif len(unit) < 256:
290 |             return from_int_to_byte(76) + from_int_to_byte(len(unit)) + unit
291 |         elif len(unit) < 65536:
292 |             return from_int_to_byte(77) + encode(len(unit), 256, 2)[::-1] + unit
293 |         else:
294 |             return from_int_to_byte(78) + encode(len(unit), 256, 4)[::-1] + unit
295 | 
296 | 
297 | if is_python2:
298 | 
299 |     def serialize_script(script):
300 |         if json_is_base(script, 16):
301 |             return binascii.hexlify(serialize_script(json_changebase(
302 |                 script, lambda x: binascii.unhexlify(x))))
303 |         return ''.join(map(serialize_script_unit, script))
304 | else:
305 | 
306 |     def serialize_script(script):
307 |         if json_is_base(script, 16):
308 |             return safe_hexlify(serialize_script(json_changebase(
309 |                 script, lambda x: binascii.unhexlify(x))))
310 | 
311 |         result = bytes()
312 |         for b in map(serialize_script_unit, script):
313 |             result += b if isinstance(b, bytes) else bytes(b, 'utf-8')
314 |         return result
315 | 
316 | 
317 | def mk_multisig_script(*args):  # [pubs],k or pub1,pub2...pub[n],k
318 |     if isinstance(args[0], list):
319 |         pubs, k = args[0], int(args[1])
320 |     else:
321 |         pubs = list(filter(lambda x: len(str(x)) >= 32, args))
322 |         k = int(args[len(pubs)])
323 |     return serialize_script([k] + pubs + [len(pubs)]) + 'ae'
324 | 
325 | # Signing and verifying
326 | 
327 | 
328 | def verify_tx_input(tx, i, script, sig, pub):
329 |     if re.match('^[0-9a-fA-F]*
#39;, tx):
330 |         tx = binascii.unhexlify(tx)
331 |     if re.match('^[0-9a-fA-F]*
#39;, script):
332 |         script = binascii.unhexlify(script)
333 |     if not re.match('^[0-9a-fA-F]*
#39;, sig):
334 |         sig = safe_hexlify(sig)
335 |     hashcode = decode(sig[-2:], 16)
336 |     modtx = signature_form(tx, int(i), script, hashcode)
337 |     return ecdsa_tx_verify(modtx, sig, pub, hashcode)
338 | 
339 | 
340 | def sign(tx, i, priv, hashcode=SIGHASH_ALL):
341 |     i = int(i)
342 |     if (not is_python2 and isinstance(re, bytes)) or not re.match(
343 |             '^[0-9a-fA-F]*
#39;, tx):
344 |         return binascii.unhexlify(sign(safe_hexlify(tx), i, priv))
345 |     if len(priv) <= 33:
346 |         priv = safe_hexlify(priv)
347 |     pub = privkey_to_pubkey(priv)
348 |     address = pubkey_to_address(pub)
349 |     signing_tx = signature_form(tx, i, mk_pubkey_script(address), hashcode)
350 |     sig = ecdsa_tx_sign(signing_tx, priv, hashcode)
351 |     txobj = deserialize(tx)
352 |     txobj["ins"][i]["script"] = serialize_script([sig, pub])
353 |     return serialize(txobj)
354 | 
355 | 
356 | def signall(tx, priv):
357 |     # if priv is a dictionary, assume format is
358 |     # { 'txinhash:txinidx' : privkey }
359 |     if isinstance(priv, dict):
360 |         for e, i in enumerate(deserialize(tx)["ins"]):
361 |             k = priv["%s:%d" % (i["outpoint"]["hash"], i["outpoint"]["index"])]
362 |             tx = sign(tx, e, k)
363 |     else:
364 |         for i in range(len(deserialize(tx)["ins"])):
365 |             tx = sign(tx, i, priv)
366 |     return tx
367 | 
368 | 
369 | def multisign(tx, i, script, pk, hashcode=SIGHASH_ALL):
370 |     if re.match('^[0-9a-fA-F]*
#39;, tx):
371 |         tx = binascii.unhexlify(tx)
372 |     if re.match('^[0-9a-fA-F]*
#39;, script):
373 |         script = binascii.unhexlify(script)
374 |     modtx = signature_form(tx, i, script, hashcode)
375 |     return ecdsa_tx_sign(modtx, pk, hashcode)
376 | 
377 | 
378 | def apply_multisignatures(*args):
379 |     # tx,i,script,sigs OR tx,i,script,sig1,sig2...,sig[n]
380 |     tx, i, script = args[0], int(args[1]), args[2]
381 |     sigs = args[3] if isinstance(args[3], list) else list(args[3:])
382 | 
383 |     if isinstance(script, str) and re.match('^[0-9a-fA-F]*
#39;, script):
384 |         script = binascii.unhexlify(script)
385 |     sigs = [binascii.unhexlify(x) if x[:2] == '30' else x for x in sigs]
386 |     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*
#39;, tx):
387 |         return safe_hexlify(apply_multisignatures(
388 |             binascii.unhexlify(tx), i, script, sigs))
389 | 
390 |     txobj = deserialize(tx)
391 |     txobj["ins"][i]["script"] = serialize_script([None] + sigs + [script])
392 |     return serialize(txobj)
393 | 
394 | 
395 | def is_inp(arg):
396 |     return len(arg) > 64 or "output" in arg or "outpoint" in arg
397 | 
398 | 
399 | def mktx(*args):
400 |     # [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ...
401 |     ins, outs = [], []
402 |     for arg in args:
403 |         if isinstance(arg, list):
404 |             for a in arg:
405 |                 (ins if is_inp(a) else outs).append(a)
406 |         else:
407 |             (ins if is_inp(arg) else outs).append(arg)
408 | 
409 |     txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []}
410 |     for i in ins:
411 |         if isinstance(i, dict) and "outpoint" in i:
412 |             txobj["ins"].append(i)
413 |         else:
414 |             if isinstance(i, dict) and "output" in i:
415 |                 i = i["output"]
416 |             txobj["ins"].append({
417 |                 "outpoint": {"hash": i[:64],
418 |                              "index": int(i[65:])},
419 |                 "script": "",
420 |                 "sequence": 4294967295
421 |             })
422 |     for o in outs:
423 |         if isinstance(o, string_or_bytes_types):
424 |             addr = o[:o.find(':')]
425 |             val = int(o[o.find(':') + 1:])
426 |             o = {}
427 |             if re.match('^[0-9a-fA-F]*
#39;, addr):
428 |                 o["script"] = addr
429 |             else:
430 |                 o["address"] = addr
431 |             o["value"] = val
432 | 
433 |         outobj = {}
434 |         if "address" in o:
435 |             outobj["script"] = address_to_script(o["address"])
436 |         elif "script" in o:
437 |             outobj["script"] = o["script"]
438 |         else:
439 |             raise Exception("Could not find 'address' or 'script' in output.")
440 |         outobj["value"] = o["value"]
441 |         txobj["outs"].append(outobj)
442 | 
443 |     return serialize(txobj)
444 | 
445 | 
446 | def select(unspent, value):
447 |     value = int(value)
448 |     high = [u for u in unspent if u["value"] >= value]
449 |     high.sort(key=lambda u: u["value"])
450 |     low = [u for u in unspent if u["value"] < value]
451 |     low.sort(key=lambda u: -u["value"])
452 |     if len(high):
453 |         return [high[0]]
454 |     i, tv = 0, 0
455 |     while tv < value and i < len(low):
456 |         tv += low[i]["value"]
457 |         i += 1
458 |     if tv < value:
459 |         raise Exception("Not enough funds")
460 |     return low[:i]
461 | 
462 | # Only takes inputs of the form { "output": blah, "value": foo }
463 | 
464 | 
465 | def mksend(*args):
466 |     argz, change, fee = args[:-2], args[-2], int(args[-1])
467 |     ins, outs = [], []
468 |     for arg in argz:
469 |         if isinstance(arg, list):
470 |             for a in arg:
471 |                 (ins if is_inp(a) else outs).append(a)
472 |         else:
473 |             (ins if is_inp(arg) else outs).append(arg)
474 | 
475 |     isum = sum([i["value"] for i in ins])
476 |     osum, outputs2 = 0, []
477 |     for o in outs:
478 |         if isinstance(o, string_types):
479 |             o2 = {"address": o[:o.find(':')], "value": int(o[o.find(':') + 1:])}
480 |         else:
481 |             o2 = o
482 |         outputs2.append(o2)
483 |         osum += o2["value"]
484 | 
485 |     if isum < osum + fee:
486 |         raise Exception("Not enough money")
487 |     elif isum > osum + fee + 5430:
488 |         outputs2 += [{"address": change, "value": isum - osum - fee}]
489 | 
490 |     return mktx(ins, outputs2)
491 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/certs/cert.crt:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIIDBjCCAe4CCQDRr9lL8wL3+jANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
 3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
 4 | cyBQdHkgTHRkMB4XDTE4MDMxMzAwMDc1OFoXDTIzMDMxMjAwMDc1OFowRTELMAkG
 5 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
 6 | IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
 7 | ALvqB0SrsncAhIkvmkLhhE4GW0MKGQognd1Vm67gnOZ4rC2+HwF3xS1xpJDZIjRJ
 8 | Tump6bp4dFoyty2vsrWJzzBib3xPh6yx37cqkQkLS7mlvl5J+VaV3vefW9Jt84Me
 9 | hT2xH62haZ6gyAI2OItyoC57uiKGJ/4TNcXZRchc3Ee1R29q5evMjJRBG6QLfHmc
10 | btsSquPwIoGxaTICNZmmZlHKct+6bBGGbJ1tJttWCeLQe//rW9KBs+Ip3heNxZpQ
11 | RZgPYwxFhZg4g4nv0CmIeMCC8KBZE82P4SRjMirVZD0YH/nNYQTEGwutZZ8F0K8Z
12 | pWR1/HDXLQVeF66SreRxp98CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATELegjEF
13 | 03r1SWSsJ2Jhg4gLDLi3tORqHFzu3S8JECi57geZKmF7NmhJHXd0vemusX9P8IRh
14 | A0B9mEU8e92k2fenqxj/t71rm+e71PBU2GwXsE5vtkX/AGmsk6KhAsbe56A3IUGK
15 | JvS3HZ2JS9MmwoUI3cHB5ExBisk4YkDripA6cIkF4i9ctQbk3bzN7FhCbj9wrKBR
16 | Lg48tbswQw4UUUZ70U5uW3pt9pPbDErSifBv6UMoY8A2s4RqiyXDyI/cxVFsY71A
17 | c+tInksyG4KAeDo919/Y0bNjgzOCt267ApQE96j4lnGCx/KSwduWBNMng6qeTecx
18 | ZOyPoABtFZcv1g==
19 | -----END CERTIFICATE-----
20 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/certs/cert.key:
--------------------------------------------------------------------------------
 1 | -----BEGIN RSA PRIVATE KEY-----
 2 | MIIEogIBAAKCAQEAu+oHRKuydwCEiS+aQuGETgZbQwoZCiCd3VWbruCc5nisLb4f
 3 | AXfFLXGkkNkiNElO6anpunh0WjK3La+ytYnPMGJvfE+HrLHftyqRCQtLuaW+Xkn5
 4 | VpXe959b0m3zgx6FPbEfraFpnqDIAjY4i3KgLnu6IoYn/hM1xdlFyFzcR7VHb2rl
 5 | 68yMlEEbpAt8eZxu2xKq4/AigbFpMgI1maZmUcpy37psEYZsnW0m21YJ4tB7/+tb
 6 | 0oGz4ineF43FmlBFmA9jDEWFmDiDie/QKYh4wILwoFkTzY/hJGMyKtVkPRgf+c1h
 7 | BMQbC61lnwXQrxmlZHX8cNctBV4XrpKt5HGn3wIDAQABAoIBAHjQSuH8nZ3i6FMn
 8 | Fr+/LAfaEFy2pkiblcNSoeg6IsYOeWxjWp3f+hZwhQRXhaUmKKUUB+BKR0wiZSDr
 9 | YDNVKa8K6nB61VjTd2jU5jBxYbs284C9gKAJdTOw8iEFbdU0DygNs7c3GqfQ6SZ6
10 | 47nL9W5NP+uoYxf4E89jFHlwMnOq3n2vW3TUIys0Op3RG40VqKEhU/XsOIjU5B7C
11 | HN2wMKMol3N62t8rbX/J7PaWxR3O017kim1Xz0fSbYA+nHk6SvA2rXMdShACcBRz
12 | t/DK4TzjwUL3a7P8Lvw8cfe0C6pBQD1sAcUxw4CQBcCs/PMCModJWoUmCflZZTqe
13 | +WdDA9kCgYEA8yvQyVhK4xMAWzCzaRjBNpFpFJAX6XAHjMmhnOCLNe2m7SQdi5p4
14 | /j65P9pjRv+nD07cv/7mrYAWXFXkLgz3WWObR4A5eWjA0gnyIHJGNa9NQ7vCWu6g
15 | aQpI+hnnwShakxuQ7w9w1WKAXh0rvnWO4hOxHlr5ve7UpEN18s7uzQMCgYEAxdPu
16 | b38Qb2tGUXWQOBYsJNvhdwevO8EPggP044ENJvQW0gmJuVsF/Yrua0TNrUv+ETYd
17 | mgK6MhNlxR6v3STwDg/OpM+MvgBafKRX2PeBJTf6k3Yx7ykxhLNPmuL6RCVv3ou9
18 | F064UzzyM9ApyWna7b0+1R1XPS+VttWZliLefPUCgYAb0iV/A7T9qczeogHEwmpI
19 | nfZRvfKeaIzUlLUCx8Xlk50HgJxIvpGdNPvozEmTc+hfHfyvkrA9pWvpgIIsqpsa
20 | BQVc9tSciVmWLkEfaTOTLM1ANJkV4jtECUM0KgaT2NQUBJFeaHvWTgC1w8yfa7+/
21 | KdWXzXzJOCvn5zf1Yat8lQKBgGgsfP+rqqzxkZrtzJ8sVdynCSiUHFvcA12U1c1D
22 | tPhRSv8Z1LON0i68jWZhWemq/cR0ecwTKZebDVlrGnLas6rD+i5huRyItR2zsSro
23 | 0tIVk1c5w3vMdm4Jup62bdGa4TkQ3uc6Jeh3TJeqQ4bzvjy5DjBNfhYTS8R24KTm
24 | AcFNAoGAPF4fOuTeFwy+a+LZ5ZTSHTiQ5YZYmnu27mM4YS0KjWCs2UKYKr47tOhf
25 | 96kuaN6zcE7gqawMTDGW+77DxTwKpbIEvKRb03PfqGnD8SHQmAQCjLOlnhW816n/
26 | +AtLlzWB7dBtgO/cW9lFIzyPCVlXRcbsn3RKS5HnLgIkuXn/kZI=
27 | -----END RSA PRIVATE KEY-----
28 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/__init__.py:
--------------------------------------------------------------------------------
 1 | from electrumpersonalserver.server.merkleproof import (
 2 |     convert_core_to_electrum_merkle_proof
 3 | )
 4 | from electrumpersonalserver.server.jsonrpc import JsonRpc, JsonRpcError
 5 | from electrumpersonalserver.server.hashes import (
 6 |     to_bytes,
 7 |     sha256,
 8 |     bh2u,
 9 |     script_to_scripthash,
10 |     get_status_electrum,
11 |     bfh,
12 |     hash_encode,
13 |     hash_decode,
14 |     Hash,
15 |     hash_merkle_root,
16 |     hash_160,
17 |     script_to_address,
18 |     address_to_script,
19 |     address_to_scripthash,
20 |     bytes_fmt,
21 | )
22 | from electrumpersonalserver.server.transactionmonitor import (
23 |     TransactionMonitor,
24 | )
25 | from electrumpersonalserver.server.deterministicwallet import (
26 |     parse_electrum_master_public_key,
27 |     DeterministicWallet,
28 |     DescriptorDeterministicWallet,
29 |     import_addresses,
30 |     ADDRESSES_LABEL,
31 | )
32 | from electrumpersonalserver.server.socks import (
33 |     socksocket,
34 |     setdefaultproxy,
35 |     PROXY_TYPE_SOCKS5,
36 | )
37 | from electrumpersonalserver.server.peertopeer import (
38 |     tor_broadcast_tx,
39 | )
40 | from electrumpersonalserver.server.electrumprotocol import (
41 |     SERVER_VERSION_NUMBER,
42 |     UnknownScripthashError,
43 |     ElectrumProtocol,
44 |     get_block_header,
45 |     get_current_header,
46 |     get_block_headers_hex,
47 |     DONATION_ADDR,
48 | )
49 | from electrumpersonalserver.server.mempoolhistogram import (
50 |     MempoolSync
51 | )
52 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/deterministicwallet.py:
--------------------------------------------------------------------------------
  1 | 
  2 | import logging
  3 | 
  4 | import electrumpersonalserver.bitcoin as btc
  5 | from electrumpersonalserver.server.hashes import bh2u, hash_160, bfh, sha256,\
  6 |     address_to_script, script_to_address
  7 | from electrumpersonalserver.server.jsonrpc import JsonRpcError
  8 | 
  9 | #the wallet types are here
 10 | #https://github.com/spesmilo/electrum/blob/3.0.6/RELEASE-NOTES
 11 | #and
 12 | #https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
 13 | 
 14 | ADDRESSES_LABEL = "electrum-watchonly-addresses"
 15 | 
 16 | def import_addresses(rpc, watchonly_addrs, wallets, change_param, count,
 17 |         logger=None):
 18 |     """
 19 |     change_param = 0 for receive, 1 for change, -1 for both
 20 |     """
 21 |     logger = logger if logger else logging.getLogger('ELECTRUMPERSONALSERVER')
 22 |     logger.debug("Importing " + str(len(watchonly_addrs)) + " watch-only "
 23 |         + "address[es] and " + str(len(wallets)) + " wallet[s] into label \""
 24 |         + ADDRESSES_LABEL + "\"")
 25 | 
 26 |     watchonly_addr_param = [{"scriptPubKey": {"address": addr}, "label":
 27 |         ADDRESSES_LABEL, "watchonly": True, "timestamp": "now"}
 28 |         for addr in watchonly_addrs]
 29 |     rpc.call("importmulti", [watchonly_addr_param, {"rescan": False}])
 30 | 
 31 |     for i, wal in enumerate(wallets):
 32 |         logger.info("Importing wallet " + str(i+1) + "/" + str(len(wallets)))
 33 |         if isinstance(wal, DescriptorDeterministicWallet):
 34 |             if change_param in (0, -1):
 35 |                 #import receive addrs
 36 |                 rpc.call("importmulti", [[{"desc": wal.descriptors[0], "range":
 37 |                     [0, count-1], "label": ADDRESSES_LABEL, "watchonly": True,
 38 |                     "timestamp": "now"}], {"rescan": False}])
 39 |             if change_param in (1, -1):
 40 |                 #import change addrs
 41 |                 rpc.call("importmulti", [[{"desc": wal.descriptors[1], "range":
 42 |                     [0, count-1], "label": ADDRESSES_LABEL, "watchonly": True,
 43 |                     "timestamp": "now"}], {"rescan": False}])
 44 |         else:
 45 |             #old-style-seed wallets
 46 |             logger.info("importing an old-style-seed wallet, will be slow...")
 47 |             for change in [0, 1]:
 48 |                 addrs, spks = wal.get_addresses(change, 0, count)
 49 |                 addr_param = [{"scriptPubKey": {"address": a}, "label":
 50 |                     ADDRESSES_LABEL, "watchonly": True, "timestamp": "now"}
 51 |                     for a in addrs]
 52 |                 rpc.call("importmulti", [addr_param, {"rescan": False}])
 53 |     logger.debug("Importing done")
 54 | 
 55 | 
 56 | def is_string_parsable_as_hex_int(s):
 57 |     try:
 58 |         int(s, 16)
 59 |         return True
 60 |     except:
 61 |         return False
 62 | 
 63 | def parse_electrum_master_public_key(keydata, gaplimit, rpc, chain):
 64 |     if chain == "main":
 65 |         xpub_vbytes = b"\x04\x88\xb2\x1e"
 66 |     elif chain == "test" or chain == "regtest" or chain == "signet":
 67 |         xpub_vbytes = b"\x04\x35\x87\xcf"
 68 |     else:
 69 |         assert False
 70 | 
 71 |     #https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
 72 | 
 73 |     descriptor_template = None
 74 |     if keydata[:4] in ("xpub", "tpub"):
 75 |         descriptor_template = "pkh({xpub}/{change}/*)"
 76 |     elif keydata[:4] in ("zpub", "vpub"):
 77 |         descriptor_template = "wpkh({xpub}/{change}/*)"
 78 |     elif keydata[:4] in ("ypub", "upub"):
 79 |         descriptor_template = "sh(wpkh({xpub}/{change}/*))"
 80 | 
 81 |     if descriptor_template != None:
 82 |         wallet = SingleSigWallet(rpc, xpub_vbytes, keydata, descriptor_template)
 83 |     elif is_string_parsable_as_hex_int(keydata) and len(keydata) == 128:
 84 |         wallet = SingleSigOldMnemonicWallet(rpc, keydata)
 85 |     elif keydata.find(" ") != -1: #multiple keys = multisig
 86 |         chunks = keydata.split(" ")
 87 |         try:
 88 |             m = int(chunks[0])
 89 |         except ValueError:
 90 |             raise ValueError("Unable to parse m in multisig key data: "
 91 |                 + chunks[0])
 92 |         pubkeys = chunks[1:]
 93 |         if not all([pubkeys[0][:4] == pub[:4] for pub in pubkeys[1:]]):
 94 |             raise ValueError("Inconsistent master public key types")
 95 |         if pubkeys[0][:4] in ("xpub", "tpub"):
 96 |             descriptor_script = "sh(sortedmulti("
 97 |         elif pubkeys[0][:4] in ("Zpub", "Vpub"):
 98 |             descriptor_script = "wsh(sortedmulti("
 99 |         elif pubkeys[0][:4] in ("Ypub", "Upub"):
100 |             descriptor_script = "sh(wsh(sortedmulti("
101 |         wallet = MultisigWallet(rpc, xpub_vbytes, m, pubkeys, descriptor_script)
102 |     else:
103 |         raise ValueError("Unrecognized electrum mpk format: " + keydata[:4])
104 |     wallet.gaplimit = gaplimit
105 |     return wallet
106 | 
107 | class DeterministicWallet(object):
108 |     def __init__(self, rpc):
109 |         self.gaplimit = 0
110 |         self.next_index = [0, 0]
111 |         self.scriptpubkey_index = {}
112 |         self.rpc = rpc
113 | 
114 |     def _derive_addresses(self, change, from_index, count):
115 |         raise RuntimeError()
116 | 
117 |     def get_addresses(self, change, from_index, count):
118 |         """Returns addresses from this deterministic wallet"""
119 |         addrs = self._derive_addresses(change, from_index, count)
120 |         spks = [address_to_script(a, self.rpc) for a in addrs]
121 |         for index, spk in enumerate(spks):
122 |             self.scriptpubkey_index[spk] = (change, from_index + index)
123 |         self.next_index[change] = max(self.next_index[change], from_index+count)
124 |         return addrs, spks
125 | 
126 |     def get_new_addresses(self, change, count):
127 |         """Returns newly-generated addresses from this deterministic wallet"""
128 |         addrs, spks = self.get_addresses(change, self.next_index[change], count)
129 |         return addrs, spks
130 | 
131 |     #called in check_for_new_txes() when a new tx of ours arrives
132 |     #to see if we need to import more addresses
133 |     def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys):
134 |         """Return None if they havent, or how many addresses to
135 |            import if they have"""
136 |         result = {}
137 |         for spk in scriptpubkeys:
138 |             if spk not in self.scriptpubkey_index:
139 |                 continue
140 |             change, index = self.scriptpubkey_index[spk]
141 |             distance_from_next = self.next_index[change] - index
142 |             if distance_from_next > self.gaplimit:
143 |                 continue
144 |             #need to import more
145 |             if change in result:
146 |                 result[change] = max(result[change], self.gaplimit
147 |                     - distance_from_next + 1)
148 |             else:
149 |                 result[change] = self.gaplimit - distance_from_next + 1
150 |         if len(result) > 0:
151 |             return result
152 |         else:
153 |             return None
154 | 
155 |     def rewind_one(self, change):
156 |         """Go back one pubkey in a branch"""
157 |         self.next_index[change] -= 1
158 | 
159 | class DescriptorDeterministicWallet(DeterministicWallet):
160 |     def __init__(self, rpc, xpub_vbytes, *args):
161 |         super(DescriptorDeterministicWallet, self).__init__(rpc)
162 |         self.xpub_vbytes = xpub_vbytes
163 | 
164 |         descriptors_without_checksum = \
165 |             self.obtain_descriptors_without_checksum(args)
166 | 
167 |         try:
168 |             self.descriptors = []
169 |             for desc in descriptors_without_checksum:
170 |                 self.descriptors.append(self.rpc.call("getdescriptorinfo",
171 |                     [desc])["descriptor"])
172 |         except JsonRpcError as e:
173 |             raise ValueError(repr(e))
174 | 
175 |     def obtain_descriptors_without_checksum(self, *args):
176 |         raise RuntimeError()
177 | 
178 |     def _derive_addresses(self, change, from_index, count):
179 |         return self.rpc.call("deriveaddresses", [self.descriptors[change], [
180 |             from_index, from_index + count - 1]])
181 |         ##the minus 1 is because deriveaddresses uses inclusive range
182 |         ##e.g. to get just the first address you use [0, 0]
183 | 
184 |     def _convert_to_standard_xpub(self, mpk):
185 |         return btc.bip32_serialize((self.xpub_vbytes, *btc.bip32_deserialize(
186 |             mpk)[1:]))
187 | 
188 | class SingleSigWallet(DescriptorDeterministicWallet):
189 |     def __init__(self, rpc, xpub_vbytes, xpub, descriptor_template):
190 |         super(SingleSigWallet, self).__init__(rpc, xpub_vbytes, xpub,
191 |             descriptor_template)
192 | 
193 |     def obtain_descriptors_without_checksum(self, args):
194 |         ##example descriptor_template:
195 |         #"pkh({xpub}/{change}/*)"
196 |         xpub, descriptor_template = args
197 | 
198 |         descriptors_without_checksum = []
199 |         xpub = self._convert_to_standard_xpub(xpub)
200 |         for change in [0, 1]:
201 |             descriptors_without_checksum.append(descriptor_template.format(
202 |                 change=change, xpub=xpub))
203 |         return descriptors_without_checksum
204 | 
205 | class MultisigWallet(DescriptorDeterministicWallet):
206 |     def __init__(self, rpc, xpub_vbytes, m, xpub_list, descriptor_script):
207 |         super(MultisigWallet, self).__init__(rpc, xpub_vbytes, m, xpub_list,
208 |             descriptor_script)
209 | 
210 |     def obtain_descriptors_without_checksum(self, args):
211 |         ##example descriptor_script:
212 |         #"sh(sortedmulti("
213 |         m, xpub_list, descriptor_script = args
214 | 
215 |         descriptors_without_checksum = []
216 |         xpub_list = [self._convert_to_standard_xpub(xpub) for xpub in xpub_list]
217 |         for change in [0, 1]:
218 |             descriptors_without_checksum.append(descriptor_script + str(m) +\
219 |                 "," + ",".join([xpub + "/" + str(change) + "/*"
220 |                 for xpub in xpub_list]) + ")"*descriptor_script.count("("))
221 |         return descriptors_without_checksum
222 | 
223 | class SingleSigOldMnemonicWallet(DeterministicWallet):
224 |     def __init__(self, rpc, mpk):
225 |         super(SingleSigOldMnemonicWallet, self).__init__(rpc)
226 |         self.mpk = mpk
227 | 
228 |     def _pubkey_to_scriptpubkey(self, pubkey):
229 |         pkh = bh2u(hash_160(bfh(pubkey)))
230 |         #op_dup op_hash_160 length hash160 op_equalverify op_checksig
231 |         return "76a914" + pkh + "88ac"
232 | 
233 |     def _derive_addresses(self, change, from_index, count):
234 |         result = []
235 |         for index in range(from_index, from_index + count):
236 |             pubkey = btc.electrum_pubkey(self.mpk, index, change)
237 |             scriptpubkey = self._pubkey_to_scriptpubkey(pubkey)
238 |             result.append(script_to_address(scriptpubkey, self.rpc))
239 |         return result
240 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/hashes.py:
--------------------------------------------------------------------------------
  1 | 
  2 | import hashlib
  3 | import binascii
  4 | import math
  5 | 
  6 | ## stuff copied from electrum's source
  7 | 
  8 | def to_bytes(something, encoding='utf8'):
  9 |     """
 10 |     cast string to bytes() like object, but for python2 support
 11 |     it's bytearray copy
 12 |     """
 13 |     if isinstance(something, bytes):
 14 |         return something
 15 |     if isinstance(something, str):
 16 |         return something.encode(encoding)
 17 |     elif isinstance(something, bytearray):
 18 |         return bytes(something)
 19 |     else:
 20 |         raise TypeError("Not a string or bytes like object")
 21 | 
 22 | def sha256(x):
 23 |     x = to_bytes(x, 'utf8')
 24 |     return bytes(hashlib.sha256(x).digest())
 25 | 
 26 | def bh2u(x):
 27 |     return binascii.hexlify(x).decode('ascii')
 28 | 
 29 | def script_to_scripthash(script):
 30 |     """Electrum uses a format hash(scriptPubKey) as the index keys"""
 31 |     h = sha256(bytes.fromhex(script))[0:32]
 32 |     return bh2u(bytes(reversed(h)))
 33 | 
 34 | #the 'result' field in the blockchain.scripthash.subscribe method
 35 | # reply uses this as a summary of the address
 36 | def get_status_electrum(h):
 37 |     if not h:
 38 |         return None
 39 |     if len(h) == 0:
 40 |         return None
 41 |     status = ''
 42 |     for tx_hash, height in h:
 43 |         status += tx_hash + ':%d:' % height
 44 |     return bh2u(hashlib.sha256(status.encode('ascii')).digest())
 45 | 
 46 | bfh = bytes.fromhex
 47 | hash_encode = lambda x: bh2u(x[::-1])
 48 | hash_decode = lambda x: bfh(x)[::-1]
 49 | 
 50 | def Hash(x):
 51 |     x = to_bytes(x, 'utf8')
 52 |     out = bytes(sha256(sha256(x)))
 53 |     return out
 54 | 
 55 | def hash_merkle_root(merkle_s, target_hash, pos):
 56 |     h = hash_decode(target_hash)
 57 |     for i in range(len(merkle_s)):
 58 |         item = merkle_s[i]
 59 |         h = Hash(hash_decode(item) + h) if ((pos >> i) & 1) else Hash(
 60 |             h + hash_decode(item))
 61 |     return hash_encode(h)
 62 | 
 63 | def hash_160(public_key):
 64 |     try:
 65 |         md = hashlib.new('ripemd160')
 66 |         md.update(sha256(public_key))
 67 |         return md.digest()
 68 |     except BaseException:
 69 |         from . import ripemd
 70 |         md = ripemd.new(sha256(public_key))
 71 |         return md.digest()
 72 | 
 73 | ## end of electrum copypaste
 74 | 
 75 | def script_to_address(scriptPubKey, rpc):
 76 |     return rpc.call("decodescript", [scriptPubKey])["address"]
 77 | 
 78 | def address_to_script(addr, rpc):
 79 |     return rpc.call("validateaddress", [addr])["scriptPubKey"]
 80 | 
 81 | def address_to_scripthash(addr, rpc):
 82 |     return script_to_scripthash(address_to_script(addr, rpc))
 83 | 
 84 | # doesnt really fit here but i dont want to clutter up server.py
 85 | 
 86 | unit_list = list(zip(['B', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2]))
 87 | 
 88 | def bytes_fmt(num):
 89 |     """Human friendly file size"""
 90 |     if num > 1:
 91 |         exponent = min(int(math.log(num, 1000)), len(unit_list) - 1)
 92 |         quotient = float(num) / 1000**exponent
 93 |         unit, num_decimals = unit_list[exponent]
 94 |         format_string = '{:.%sf} {}' % (num_decimals)
 95 |         return format_string.format(quotient, unit)
 96 |     if num == 0:
 97 |         return '0 bytes'
 98 |     if num == 1:
 99 |         return '1 byte'
100 | 
101 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/jsonrpc.py:
--------------------------------------------------------------------------------
  1 | # Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu>
  2 | # Copyright (C) 2014 by phelix / blockchained.com
  3 | 
  4 | #jsonrpc.py from https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/jsonrpc.py
  5 | 
  6 | import base64
  7 | import http.client
  8 | import json
  9 | 
 10 | class JsonRpcError(Exception): pass
 11 | class JsonRpcConnectionError(JsonRpcError): pass
 12 | 
 13 | class JsonRpc(object):
 14 |     """
 15 |     Simple implementation of a JSON-RPC client that is used
 16 |     to connect to Bitcoin.
 17 |     """
 18 |     def __init__(self, host, port, user, password, cookie_path=None,
 19 |             wallet_filename="", logger=None):
 20 |         self.host = host
 21 |         self.port = port
 22 | 
 23 |         self.cookie_path = cookie_path
 24 |         if cookie_path:
 25 |             self.load_from_cookie()
 26 |         else:
 27 |             self.create_authstr(user, password)
 28 | 
 29 |         self.conn = http.client.HTTPConnection(self.host, self.port)
 30 |         if len(wallet_filename) > 0:
 31 |             self.url = "/wallet/" + wallet_filename
 32 |         else:
 33 |             self.url = ""
 34 |         self.logger = logger
 35 |         self.queryId = 1
 36 | 
 37 |     def create_authstr(self, username, password):
 38 |         self.authstr = "%s:%s" % (username, password)
 39 | 
 40 |     def load_from_cookie(self):
 41 |         fd = open(self.cookie_path)
 42 |         username, password = fd.read().strip().split(":")
 43 |         fd.close()
 44 |         self.create_authstr(username, password)
 45 | 
 46 |     def queryHTTP(self, obj):
 47 |         """
 48 |         Send an appropriate HTTP query to the server.  The JSON-RPC
 49 |         request should be (as object) in 'obj'.  If the call succeeds,
 50 |         the resulting JSON object is returned.  In case of an error
 51 |         with the connection (not JSON-RPC itself), an exception is raised.
 52 |         """
 53 |         headers = {"User-Agent": "electrum-personal-server",
 54 |                    "Content-Type": "application/json",
 55 |                    "Accept": "application/json"}
 56 |         headers["Authorization"] = (b"Basic " +
 57 |             base64.b64encode(self.authstr.encode('utf-8')))
 58 |         body = json.dumps(obj)
 59 |         auth_failed_once = False
 60 |         for i in range(20):
 61 |             try:
 62 |                 self.conn.request("POST", self.url, body, headers)
 63 |                 response = self.conn.getresponse()
 64 |                 if response.status == 401:
 65 |                     if self.cookie_path == None or auth_failed_once:
 66 |                         self.conn.close()
 67 |                         raise JsonRpcConnectionError(
 68 |                                 "authentication for JSON-RPC failed")
 69 |                     else:
 70 |                         auth_failed_once = True
 71 |                         #try reloading u/p from the cookie file once
 72 |                         self.load_from_cookie()
 73 |                         raise OSError() #jump to error handler below
 74 |                 auth_failed_once = False
 75 |                 #All the codes below are 'fine' from a JSON-RPC point of view.
 76 |                 if response.status not in [200, 404, 500]:
 77 |                     self.conn.close()
 78 |                     raise JsonRpcConnectionError("unknown error in JSON-RPC")
 79 |                 data = response.read()
 80 |                 return json.loads(data.decode('utf-8'))
 81 |             except JsonRpcConnectionError as exc:
 82 |                 raise exc
 83 |             except http.client.BadStatusLine:
 84 |                 return "CONNFAILURE"
 85 |             except OSError:
 86 |                     # connection dropped, reconnect
 87 |                     try:
 88 |                         self.conn.close()
 89 |                         self.conn.connect()
 90 |                     except ConnectionError as e:
 91 |                         #node probably offline, notify with jsonrpc error
 92 |                         raise JsonRpcConnectionError(repr(e))
 93 |                     continue
 94 |             except Exception as exc:
 95 |                 raise JsonRpcConnectionError("JSON-RPC connection failed. Err:"
 96 |                     + repr(exc))
 97 |             break
 98 |         return None
 99 | 
100 |     def call(self, method, params):
101 |         currentId = self.queryId
102 |         self.queryId += 1
103 | 
104 |         request = {"method": method, "params": params, "id": currentId}
105 |         #query can fail from keepalive timeout; keep retrying if it does, up
106 |         #to a reasonable limit, then raise (failure to access blockchain
107 |         #is a critical failure). Note that a real failure to connect (e.g.
108 |         #wrong port) is raised in queryHTTP directly.
109 |         response_received = False
110 |         for i in range(100):
111 |             response = self.queryHTTP(request)
112 |             if response != "CONNFAILURE":
113 |                 response_received = True
114 |                 break
115 |             #Failure means keepalive timed out, just make a new one
116 |             self.conn = http.client.HTTPConnection(self.host, self.port)
117 |         if not response_received:
118 |             raise JsonRpcConnectionError("Unable to connect over RPC")
119 |         if response["id"] != currentId:
120 |             raise JsonRpcConnectionError("invalid id returned by query")
121 |         if response["error"] is not None:
122 |             raise JsonRpcError(response["error"])
123 |         return response["result"]
124 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/mempoolhistogram.py:
--------------------------------------------------------------------------------
  1 | 
  2 | import time
  3 | from collections import defaultdict
  4 | from datetime import datetime
  5 | from enum import Enum
  6 | 
  7 | from electrumpersonalserver.server.jsonrpc import JsonRpcError
  8 | 
  9 | def calc_histogram(mempool):
 10 |     #algorithm copied from the relevant place in ElectrumX
 11 |     #https://github.com/kyuupichan/electrumx/blob/e92c9bd4861c1e35989ad2773d33e01219d33280/server/mempool.py
 12 |     fee_hist = defaultdict(int)
 13 |     for fee_rate, size in mempool.values():
 14 |         fee_hist[fee_rate] += size
 15 |     l = list(reversed(sorted(fee_hist.items())))
 16 |     out = []
 17 |     size = 0
 18 |     r = 0
 19 |     binsize = 100000
 20 |     for fee, s in l:
 21 |         size += s
 22 |         if size + r > binsize:
 23 |             out.append((fee, size))
 24 |             r += size - binsize
 25 |             size = 0
 26 |             binsize *= 1.1
 27 |     return out
 28 | 
 29 | class PollIntervalChange(Enum):
 30 |     UNCHANGED = "unchanged"
 31 |     FAST_POLLING = "fastpolling"
 32 |     NORMAL_POLLING = "normalpolling"
 33 | 
 34 | class MempoolSync(object):
 35 |     def __init__(self, rpc, disabled, polling_interval):
 36 |         self.rpc = rpc
 37 |         self.disabled = disabled
 38 |         self.polling_interval = polling_interval
 39 |         self.mempool = dict()
 40 |         self.cached_fee_histogram = [[0, 0]]
 41 |         self.added_txids = None
 42 |         self.last_poll = None
 43 |         self.state = "gettxids"
 44 | 
 45 |     def set_polling_interval(self, polling_interval):
 46 |         self.polling_interval = polling_interval
 47 | 
 48 |     def get_fee_histogram(self):
 49 |         return self.cached_fee_histogram
 50 | 
 51 |     def initial_sync(self, logger):
 52 |         if self.disabled:
 53 |             return
 54 |         logger.info("Synchronizing mempool . . .")
 55 |         st = time.time()
 56 |         for _ in range(2):
 57 |             self.poll_update(-1)
 58 |         self.state = "gettxids"
 59 |         for _ in range(2):
 60 |             self.poll_update(-1)
 61 |         #run once for the getrawmempool
 62 |         #again for the getmempoolentry
 63 |         #and all that again because the first time will take so long
 64 |         # that new txes could arrive in that time
 65 |         et = time.time()
 66 |         logger.info("Found " + str(len(self.mempool)) + " mempool entries. "
 67 |             + "Synchronized mempool in " + str(et - st) + "sec")
 68 | 
 69 |     #-1 for no timeout
 70 |     def poll_update(self, timeout):
 71 |         poll_interval_change = PollIntervalChange.UNCHANGED
 72 |         if self.disabled:
 73 |             return poll_interval_change
 74 |         if self.state == "waiting":
 75 |             if ((datetime.now() - self.last_poll).total_seconds()
 76 |                     > self.polling_interval):
 77 |                 poll_interval_change = PollIntervalChange.FAST_POLLING
 78 |                 self.state = "gettxids"
 79 |         elif self.state == "gettxids":
 80 |             mempool_txids = self.rpc.call("getrawmempool", [])
 81 |             self.last_poll = datetime.now()
 82 |             mempool_txids = set(mempool_txids)
 83 | 
 84 |             removed_txids = set(self.mempool.keys()).difference(mempool_txids)
 85 |             self.added_txids = iter(mempool_txids.difference(
 86 |                 set(self.mempool.keys())))
 87 | 
 88 |             for txid in removed_txids:
 89 |                 del self.mempool[txid]
 90 | 
 91 |             self.state = "getfeerates"
 92 |         elif self.state == "getfeerates":
 93 |             if timeout == -1:
 94 |                 timeout = 2**32
 95 |             start_time = datetime.now()
 96 |             while self.state != "waiting" and ((datetime.now() - start_time
 97 |                     ).total_seconds() < timeout):
 98 |                 try:
 99 |                     txid = next(self.added_txids)
100 |                 except StopIteration:
101 |                     self.cached_fee_histogram = calc_histogram(self.mempool)
102 |                     self.state = "waiting"
103 |                     poll_interval_change = \
104 |                         PollIntervalChange.NORMAL_POLLING
105 |                     self.last_poll = datetime.now()
106 |                     continue
107 |                 try:
108 |                     mempool_tx = self.rpc.call("getmempoolentry", [txid])
109 |                 except JsonRpcError:
110 |                     continue
111 |                 fee_rate = 1e8*mempool_tx["fees"]["base"] // mempool_tx["vsize"]
112 |                 self.mempool[txid] = (fee_rate, mempool_tx["vsize"])
113 | 
114 |         return poll_interval_change
115 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/merkleproof.py:
--------------------------------------------------------------------------------
  1 | 
  2 | import electrumpersonalserver.bitcoin as btc
  3 | import binascii
  4 | from math import ceil, log
  5 | 
  6 | from electrumpersonalserver.server.hashes import Hash, hash_encode, hash_decode
  7 | 
  8 | #lots of ideas and code taken from bitcoin core and breadwallet
  9 | #https://github.com/bitcoin/bitcoin/blob/master/src/merkleblock.h
 10 | #https://github.com/breadwallet/breadwallet-core/blob/master/BRMerkleBlock.c
 11 | 
 12 | def calc_tree_width(height, txcount):
 13 |     """Efficently calculates the number of nodes at given merkle tree height"""
 14 |     return (txcount + (1 << height) - 1) >> height
 15 | 
 16 | def decend_merkle_tree(hashes, flags, height, txcount, pos):
 17 |     """Function recursively follows the flags bitstring down into the
 18 |        tree, building up a tree in memory"""
 19 |     flag = next(flags)
 20 |     if height > 0:
 21 |         #non-txid node
 22 |         if flag:
 23 |             left = decend_merkle_tree(hashes, flags, height-1, txcount, pos*2)
 24 |             #bitcoin's merkle tree format has a rule that if theres an
 25 |             # odd number of nodes in then the tree, the last hash is duplicated
 26 |             #in the electrum format we must hash together the duplicate
 27 |             # tree branch
 28 |             if pos*2+1 < calc_tree_width(height-1, txcount):
 29 |                 right = decend_merkle_tree(hashes, flags, height-1,
 30 |                     txcount, pos*2+1)
 31 |             else:
 32 |                 if isinstance(left, tuple):
 33 |                     right = expand_tree_hashing(left)
 34 |                 else:
 35 |                     right = left
 36 |             return (left, right)
 37 |         else:
 38 |             hs = next(hashes)
 39 |             return hs
 40 |     else:
 41 |         #txid node
 42 |         hs = next(hashes)
 43 |         if flag:
 44 |             #for the actual transaction, also store its position with a flag 
 45 |             return "tx:" + str(pos) + ":" + hs
 46 |         else:
 47 |             return hs
 48 | 
 49 | def deserialize_core_format_merkle_proof(hash_list, flag_value, txcount):
 50 |     """Converts core's format for a merkle proof into a tree in memory"""
 51 |     tree_depth = int(ceil(log(txcount, 2)))
 52 |     hashes = iter(hash_list)
 53 |     #one-liner which converts the flags value to a list of True/False bits
 54 |     flags = (flag_value[i//8]&1 << i%8 != 0 for i in range(len(flag_value)*8))
 55 |     try:
 56 |         root_node = decend_merkle_tree(hashes, flags, tree_depth, txcount, 0)
 57 |         return root_node
 58 |     except StopIteration:
 59 |         raise ValueError
 60 | 
 61 | def expand_tree_electrum_format_merkle_proof(node, result):
 62 |     """Recurse down into the tree, adding hashes to the result list
 63 |        in depth order"""
 64 |     left, right = node
 65 |     if isinstance(left, tuple):
 66 |         expand_tree_electrum_format_merkle_proof(left, result)
 67 |     if isinstance(right, tuple):
 68 |         expand_tree_electrum_format_merkle_proof(right, result)
 69 |     if not isinstance(left, tuple):
 70 |         result.append(left)
 71 |     if not isinstance(right, tuple):
 72 |         result.append(right)
 73 | 
 74 | def get_node_hash(node):
 75 |     if node.startswith("tx"):
 76 |         return node.split(":")[2]
 77 |     else:
 78 |         return node
 79 | 
 80 | def expand_tree_hashing(node):
 81 |     """Recurse down into the tree, hashing everything and
 82 |        returning root hash"""
 83 |     left, right = node
 84 |     if isinstance(left, tuple):
 85 |         hash_left = expand_tree_hashing(left)
 86 |     else:
 87 |         hash_left = get_node_hash(left)
 88 |     if isinstance(right, tuple):
 89 |         hash_right = expand_tree_hashing(right)
 90 |     else:
 91 |         hash_right = get_node_hash(right)
 92 |     return hash_encode(Hash(hash_decode(hash_left) + hash_decode(hash_right)))
 93 | 
 94 | def convert_core_to_electrum_merkle_proof(proof):
 95 |     """Bitcoin Core and Electrum use different formats for merkle
 96 |        proof, this function converts from Core's format to Electrum's format"""
 97 |     proof = binascii.unhexlify(proof)
 98 |     pos = [0]
 99 |     def read_as_int(bytez):
100 |         pos[0] += bytez
101 |         return btc.decode(proof[pos[0] - bytez:pos[0]][::-1], 256)
102 |     def read_var_int():
103 |         pos[0] += 1
104 |         val = btc.from_byte_to_int(proof[pos[0] - 1])
105 |         if val < 253:
106 |             return val
107 |         return read_as_int(pow(2, val - 252))
108 |     def read_bytes(bytez):
109 |         pos[0] += bytez
110 |         return proof[pos[0] - bytez:pos[0]]
111 | 
112 |     merkle_root = proof[36:36+32]
113 |     pos[0] = 80
114 |     txcount = read_as_int(4)
115 |     hash_count = read_var_int()
116 |     hashes = [hash_encode(read_bytes(32)) for i in range(hash_count)]
117 |     flags_count = read_var_int()
118 |     flags = read_bytes(flags_count)
119 | 
120 |     root_node = deserialize_core_format_merkle_proof(hashes, flags, txcount)
121 |     #check special case of a tree of zero height, block with only coinbase tx
122 |     if not isinstance(root_node, tuple):
123 |         root_node = root_node[5:] #remove the "tx:0:"
124 |         result = {"pos": 0, "merkle": [], "txid": root_node,
125 |             "merkleroot": hash_encode(merkle_root)}
126 |         return result
127 | 
128 |     hashes_list = []
129 |     expand_tree_electrum_format_merkle_proof(root_node, hashes_list)
130 |     #remove the first or second element which is the txhash
131 |     tx = hashes_list[0]
132 |     if hashes_list[1].startswith("tx"):
133 |         tx = hashes_list[1]
134 |     assert(tx.startswith("tx"))
135 |     hashes_list.remove(tx)
136 |     #if the txhash was duplicated, that _is_ included in electrum's format
137 |     if hashes_list[0].startswith("tx"):
138 |         hashes_list[0] = tx.split(":")[2]
139 |     tx_pos, txid = tx.split(":")[1:3]
140 |     tx_pos = int(tx_pos)
141 |     result = {"pos": tx_pos, "merkle": hashes_list, "txid": txid,
142 |         "merkleroot": hash_encode(merkle_root)}
143 |     return result
144 | 
145 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/peertopeer.py:
--------------------------------------------------------------------------------
  1 | #! /usr/bin/env python
  2 | 
  3 | import socket
  4 | import time
  5 | import base64
  6 | import threading
  7 | import queue
  8 | import random
  9 | from struct import pack, unpack
 10 | from datetime import datetime
 11 | 
 12 | import electrumpersonalserver.bitcoin as btc
 13 | from electrumpersonalserver.server.socks import (
 14 |     socksocket,
 15 |     setdefaultproxy,
 16 |     PROXY_TYPE_SOCKS5
 17 | )
 18 | from electrumpersonalserver.server.jsonrpc import JsonRpcError
 19 | 
 20 | PROTOCOL_VERSION = 70016
 21 | DEFAULT_USER_AGENT = '/Satoshi:22.0.0/'
 22 | 
 23 | #https://github.com/bitcoin/bitcoin/blob/master/src/protocol.h
 24 | NODE_NETWORK = 1
 25 | NODE_BLOOM = 1 << 2
 26 | NODE_WITNESS = 1 << 3
 27 | NODE_NETWORK_LIMITED = 1 << 10
 28 | 
 29 | # protocol versions above this also send a relay boolean
 30 | RELAY_TX_VERSION = 70001
 31 | 
 32 | # length of bitcoin p2p packets
 33 | HEADER_LENGTH = 24
 34 | 
 35 | # if no message has been seen for this many seconds, send a ping
 36 | KEEPALIVE_INTERVAL = 2 * 60
 37 | 
 38 | # close connection if keep alive ping isnt responded to in this many seconds
 39 | KEEPALIVE_TIMEOUT = 20 * 60
 40 | 
 41 | 
 42 | def ip_to_hex(ip_str):
 43 |     # ipv4 only for now
 44 |     return socket.inet_pton(socket.AF_INET, ip_str)
 45 | 
 46 | def create_net_addr(hexip, port): # doesnt contain time as in bitcoin wiki
 47 |     services = 0
 48 |     hex = bytes(10) + b'\xFF\xFF' + hexip
 49 |     return pack('<Q16s', services, hex) + pack('>H', port)
 50 | 
 51 | def create_var_str(s):
 52 |     return btc.num_to_var_int(len(s)) + s.encode()
 53 | 
 54 | def read_int(ptr, payload, n, littleendian=True):
 55 |     data = payload[ptr[0] : ptr[0]+n]
 56 |     if littleendian:
 57 |         data = data[::-1]
 58 |     ret =  btc.decode(data, 256)
 59 |     ptr[0] += n
 60 |     return ret
 61 | 
 62 | def read_var_int(ptr, payload):
 63 |     val = payload[ptr[0]]
 64 |     ptr[0] += 1
 65 |     if val < 253:
 66 |         return val
 67 |     return read_int(ptr, payload, 2**(val - 252))
 68 | 
 69 | def read_var_str(ptr, payload):
 70 |     l = read_var_int(ptr, payload)
 71 |     ret = payload[ptr[0]: ptr[0] + l]
 72 |     ptr[0] += l
 73 |     return ret
 74 | 
 75 | def ip_hex_to_str(ip_hex):
 76 |     # https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
 77 |     # https://www.cypherpunk.at/onioncat_trac/wiki/OnionCat
 78 |     if ip_hex[:14] == '\x00'*10 + '\xff'*2:
 79 |         # ipv4 mapped ipv6 addr
 80 |         return socket.inet_ntoa(ip_hex[12:])
 81 |     elif ip_hex[:6] == '\xfd\x87\xd8\x7e\xeb\x43':
 82 |         return base64.b32encode(ip_hex[6:]).lower() + '.onion'
 83 |     else:
 84 |         return socket.inet_ntop(socket.AF_INET6, ip_hex)
 85 | 
 86 | class P2PMessageHandler(object):
 87 |     def __init__(self, logger):
 88 |         self.last_message = datetime.now()
 89 |         self.waiting_for_keepalive = False
 90 |         self.logger = logger
 91 | 
 92 |     def check_keepalive(self, p2p):
 93 |         if self.waiting_for_keepalive:
 94 |             if ((datetime.now() - self.last_message).total_seconds()
 95 |                     < KEEPALIVE_TIMEOUT):
 96 |                 return
 97 |             self.logger.debug('keepalive timed out, closing')
 98 |             p2p.sock.close()
 99 |         else:
100 |             if ((datetime.now() - self.last_message).total_seconds()
101 |                     < KEEPALIVE_INTERVAL):
102 |                 return
103 |             self.logger.debug('sending keepalive to peer')
104 |             self.waiting_for_keepalive = True
105 |             p2p.sock.sendall(p2p.create_message('ping', '\x00'*8))
106 | 
107 |     def handle_message(self, p2p, command, length, payload):
108 |         self.last_message = datetime.now()
109 |         self.waiting_for_keepalive = False
110 |         ptr = [0]
111 |         if command == b'version':
112 |             version = read_int(ptr, payload, 4)
113 |             services = read_int(ptr, payload, 8)
114 |             timestamp = read_int(ptr, payload, 8)
115 |             addr_recv_services = read_int(ptr, payload, 8)
116 |             addr_recv_ip = payload[ptr[0] : ptr[0]+16]
117 |             ptr[0] += 16
118 |             addr_recv_port = read_int(ptr, payload, 2, False)
119 |             addr_trans_services = read_int(ptr, payload, 8)
120 |             addr_trans_ip = payload[ptr[0] : ptr[0]+16]
121 |             ptr[0] += 16
122 |             addr_trans_port = read_int(ptr, payload, 2, False)
123 |             ptr[0] += 8 # skip over nonce
124 |             user_agent = read_var_str(ptr, payload)
125 |             start_height = read_int(ptr, payload, 4)
126 |             if version > RELAY_TX_VERSION:
127 |                 relay = read_int(ptr, payload, 1) != 0
128 |             else:
129 |                 # must check node accepts unconfirmed txes before broadcasting
130 |                 relay = True
131 |             self.logger.debug(('Received peer version message: version=%d'
132 |                 + ' services=0x%x'
133 |                 + ' timestamp=%s user_agent=%s start_height=%d relay=%i'
134 |                 + ' them=%s:%d us=%s:%d') % (version,
135 |                 services, str(datetime.fromtimestamp(timestamp)),
136 |                 user_agent, start_height, relay, ip_hex_to_str(addr_trans_ip)
137 |                 , addr_trans_port, ip_hex_to_str(addr_recv_ip), addr_recv_port))
138 |             p2p.sock.sendall(p2p.create_message('verack', b''))
139 |             self.on_recv_version(p2p, version, services, timestamp,
140 |                 addr_recv_services, addr_recv_ip, addr_trans_services,
141 |                 addr_trans_ip, addr_trans_port, user_agent, start_height,
142 |                 relay)
143 |         elif command == b'verack':
144 |             self.on_connected(p2p)
145 |         elif command == b'ping':
146 |             p2p.sock.sendall(p2p.create_message('pong', payload))
147 | 
148 |     # optional override these in a subclass
149 | 
150 |     def on_recv_version(self, p2p, version, services, timestamp,
151 |             addr_recv_services, addr_recv_ip, addr_trans_services,
152 |             addr_trans_ip, addr_trans_port, user_agent, start_height, relay):
153 |         pass
154 | 
155 |     def on_connected(self, p2p):
156 |         pass
157 | 
158 |     def on_heartbeat(self, p2p):
159 |         pass
160 | 
161 | 
162 | class P2PProtocol(object):
163 |     def __init__(self, p2p_message_handler, remote_hostport,
164 |                  network, logger, notify_queue, user_agent=DEFAULT_USER_AGENT,
165 |                  socks5_hostport=("localhost", 9050), connect_timeout=30,
166 |                  heartbeat_interval=15, start_height=0):
167 |         self.p2p_message_handler = p2p_message_handler
168 |         self.remote_hostport = remote_hostport
169 |         self.logger = logger
170 |         self.notify_queue = notify_queue
171 |         self.user_agent = user_agent
172 |         self.socks5_hostport = socks5_hostport
173 |         self.connect_timeout = connect_timeout
174 |         self.heartbeat_interval = heartbeat_interval
175 |         self.start_height = start_height
176 |         if network == "testnet":
177 |             self.magic = 0x0709110b
178 |         elif network == "regtest":
179 |             self.magic = 0xdab5bffa
180 |         else:
181 |             self.magic = 0xd9b4bef9
182 |         self.closed = False
183 | 
184 |     def run(self):
185 |         services = (NODE_NETWORK | NODE_WITNESS | NODE_NETWORK_LIMITED)
186 |         st = int(time.time())
187 |         nonce = random.getrandbits(64)
188 | 
189 |         netaddr_them = create_net_addr(ip_to_hex('0.0.0.0'), 0)
190 |         netaddr_us = create_net_addr(ip_to_hex('0.0.0.0'), 0)
191 |         version_message = (pack('<iQQ', PROTOCOL_VERSION, services, st)
192 |                            + netaddr_them
193 |                            + netaddr_us
194 |                            + pack('<Q', nonce)
195 |                            + create_var_str(self.user_agent)
196 |                            + pack('<I', self.start_height)
197 |                            + b'\x01')
198 | 
199 |         self.logger.debug('Connecting to bitcoin peer at ' +
200 |                 str(self.remote_hostport) + ' with proxy ' +
201 |                 str(self.socks5_hostport))
202 |         setdefaultproxy(PROXY_TYPE_SOCKS5, self.socks5_hostport[0],
203 |                         self.socks5_hostport[1], True)
204 |         self.sock = socksocket()
205 |         self.sock.settimeout(self.connect_timeout)
206 |         self.sock.connect(self.remote_hostport)
207 |         self.sock.sendall(self.create_message('version', version_message))
208 | 
209 |         self.sock.settimeout(self.heartbeat_interval)
210 |         self.closed = False
211 |         try:
212 |             recv_buffer = b''
213 |             payload_length = -1  # -1 means waiting for header
214 |             command = None
215 |             checksum = None
216 |             while not self.closed:
217 |                 try:
218 |                     recv_data = self.sock.recv(4096)
219 |                     if not recv_data or len(recv_data) == 0:
220 |                         raise EOFError()
221 |                     recv_buffer += recv_data
222 |                     # this is O(N^2) scaling in time, another way would be to
223 |                     # store in a list and combine at the end with "".join()
224 |                     # but this isnt really timing critical so didnt optimize it
225 | 
226 |                     data_remaining = True
227 |                     while data_remaining and not self.closed:
228 |                         if payload_length == -1 and (len(recv_buffer)
229 |                                 >= HEADER_LENGTH):
230 |                             net_magic, command, payload_length, checksum =\
231 |                                 unpack('<I12sI4s', recv_buffer[:HEADER_LENGTH])
232 |                             recv_buffer = recv_buffer[HEADER_LENGTH:]
233 |                             if net_magic != self.magic:
234 |                                 self.logger.debug('wrong MAGIC: ' +
235 |                                     hex(net_magic))
236 |                                 self.sock.close()
237 |                                 break
238 |                             command = command.strip(b'\0')
239 |                         else:
240 |                             if payload_length >= 0 and (len(recv_buffer)
241 |                                     >= payload_length):
242 |                                 payload = recv_buffer[:payload_length]
243 |                                 recv_buffer = recv_buffer[payload_length:]
244 |                                 if btc.bin_dbl_sha256(payload)[:4] == checksum:
245 |                                     self.p2p_message_handler.handle_message(
246 |                                         self, command, payload_length, payload)
247 |                                 else:
248 |                                     self.logger.debug("wrong checksum, " +
249 |                                         "dropping " +
250 |                                         "message, cmd=" + command +
251 |                                         " payloadlen=" + str(payload_length))
252 |                                 payload_length = -1
253 |                                 data_remaining = True
254 |                             else:
255 |                                 data_remaining = False
256 |                 except socket.timeout:
257 |                     self.p2p_message_handler.check_keepalive(self)
258 |                     self.p2p_message_handler.on_heartbeat(self)
259 |         except EOFError as e:
260 |             self.closed = True
261 |         except IOError as e:
262 |             import traceback
263 |             self.logger.debug("logging traceback from %s: \n" %
264 |                 traceback.format_exc())
265 |             self.closed = True
266 |         finally:
267 |             try:
268 |                 self.sock.close()
269 |             except Exception as _:
270 |                 pass
271 | 
272 |     def close(self):
273 |         self.closed = True
274 | 
275 |     def create_message(self, command, payload):
276 |         return (pack("<I12sI", self.magic, command.encode(), len(payload))
277 |             + btc.bin_dbl_sha256(payload)[:4] + payload)
278 | 
279 | class P2PBroadcastTx(P2PMessageHandler):
280 |     def __init__(self, txhex, logger, notify_queue):
281 |         P2PMessageHandler.__init__(self, logger)
282 |         self.txhex = bytes.fromhex(txhex)
283 |         self.txid = btc.bin_txhash(self.txhex)
284 |         self.uploaded_tx = False
285 |         self.time_marker = datetime.now()
286 |         self.connected = False
287 |         self.notify_queue = notify_queue
288 | 
289 |     def on_recv_version(self, p2p, version, services, timestamp,
290 |             addr_recv_services, addr_recv_ip, addr_trans_services,
291 |             addr_trans_ip, addr_trans_port, user_agent, start_height, relay):
292 |         if not relay:
293 |             self.logger.debug('peer not accepting unconfirmed txes, trying ' +
294 |                 'another')
295 |             # this happens if the other node is using blockonly=1
296 |             p2p.close()
297 |         if not services & NODE_WITNESS:
298 |             self.logger.debug('peer not accepting witness data, trying another')
299 |             p2p.close()
300 | 
301 |     def on_connected(self, p2p):
302 |         MSG = 1 #msg_tx
303 |         inv_payload = pack('<BI', 1, MSG) + self.txid
304 |         p2p.sock.sendall(p2p.create_message('inv', inv_payload))
305 |         self.time_marker = datetime.now()
306 |         self.uploaded_tx = False
307 |         self.connected = True
308 | 
309 |     def on_heartbeat(self, p2p):
310 |         self.logger.debug('broadcaster heartbeat')
311 |         VERACK_TIMEOUT = 40
312 |         GETDATA_TIMEOUT = 60
313 |         if not self.connected:
314 |             if ((datetime.now() - self.time_marker).total_seconds()
315 |                     < VERACK_TIMEOUT):
316 |                 return
317 |             self.logger.debug('timed out of waiting for verack')
318 |         else:
319 |             if ((datetime.now() - self.time_marker).total_seconds()
320 |                     < GETDATA_TIMEOUT):
321 |                 return
322 |             self.logger.debug('timed out in waiting for getdata, node ' +
323 |                 'already has tx')
324 |             self.uploaded_tx = True
325 |         p2p.close()
326 | 
327 |     def handle_message(self, p2p, command, length, payload):
328 |         P2PMessageHandler.handle_message(self, p2p, command, length, payload)
329 |         ptr = [0]
330 |         if command == b'getdata':
331 |             count = read_var_int(ptr, payload)
332 |             for _ in range(count):
333 |                 ptr[0] += 4
334 |                 hash_id = payload[ptr[0] : ptr[0] + 32]
335 |                 ptr[0] += 32
336 |                 if hash_id == self.txid:
337 |                     p2p.sock.sendall(p2p.create_message('tx', self.txhex))
338 |                     self.uploaded_tx = True
339 |                     self.logger.debug("Uploaded transaction via tor to peer at "
340 |                         + str(p2p.remote_hostport))
341 |                     self.notify_queue.put(True)
342 |                     ##make sure the packets really got through by sleeping
343 |                     ##some kernels seem to send a RST packet on close() even
344 |                     ##if theres still data in the send buffer
345 |                     time.sleep(5)
346 |                     p2p.close()
347 | 
348 | def broadcaster_thread(txhex, node_addrs, tor_hostport, network, logger,
349 |         start_height, notify_queue):
350 |     for node_addr in node_addrs:
351 |         remote_hostport = (node_addr["address"], node_addr["port"])
352 |         p2p_msg_handler = P2PBroadcastTx(txhex, logger, notify_queue)
353 |         p2p = P2PProtocol(p2p_msg_handler, remote_hostport,
354 |             network, logger, notify_queue, socks5_hostport=tor_hostport,
355 |             heartbeat_interval=20, start_height=start_height)
356 |         try:
357 |             p2p.run()
358 |         except IOError as e:
359 |             logger.debug("p2p.run() exited: " + repr(e))
360 |             continue
361 |         if p2p_msg_handler.uploaded_tx:
362 |             break
363 |     logger.debug("Exiting tor broadcast thread, uploaded_tx = " +
364 |         str(p2p_msg_handler.uploaded_tx))
365 |     if not p2p_msg_handler.uploaded_tx:
366 |         notify_queue.put(False)
367 |     return p2p_msg_handler.uploaded_tx
368 | 
369 | def chunk_list(d, n):
370 |     return [d[x:x + n] for x in range(0, len(d), n)]
371 | 
372 | def tor_broadcast_tx(txhex, tor_hostport, network, rpc, logger):
373 |     CONNECTION_THREADS = 8
374 |     CONNECTION_ATTEMPTS_PER_THREAD = 10
375 | 
376 |     required_address_count = CONNECTION_ATTEMPTS_PER_THREAD * CONNECTION_THREADS
377 |     node_addrs_witness = []
378 |     while True:
379 |         try:
380 |             new_node_addrs = rpc.call("getnodeaddresses",
381 |                 [3*required_address_count//2])
382 |         except JsonRpcError as e:
383 |             logger.debug(repr(e))
384 |             logger.error("Bitcoin Core v0.18.0 or higher is required "
385 |                 "to broadcast through Tor")
386 |             return False
387 |         node_addrs_witness.extend(
388 |             [a for a in new_node_addrs if a["services"] & NODE_WITNESS]
389 |         )
390 |         logger.debug("len(new_node_addrs) = " + str(len(new_node_addrs)) +
391 |             " len(node_addrs_witness) = " + str(len(node_addrs_witness)))
392 |         if len(node_addrs_witness) > required_address_count:
393 |             break
394 |     node_addrs_chunks = chunk_list(
395 |         node_addrs_witness[:required_address_count],
396 |         CONNECTION_ATTEMPTS_PER_THREAD
397 |     )
398 |     notify_queue = queue.Queue()
399 |     start_height = rpc.call("getblockcount", [])
400 |     for node_addrs in node_addrs_chunks:
401 |         t = threading.Thread(target=broadcaster_thread,
402 |             args=(txhex, node_addrs, tor_hostport, network, logger,
403 |                 start_height, notify_queue),
404 |             daemon=True)
405 |         t.start()
406 |     try:
407 |         success = notify_queue.get(block=True, timeout=20)
408 |     except queue.Empty:
409 |         logger.debug("Timed out getting notification for broadcasting "
410 |             + "transaction")
411 |         #the threads will maybe still continue to try broadcasting even
412 |         # after this timeout
413 |         #could time out at 20 seconds for any legitimate reason, tor is slow
414 |         # so no point failing, this timeout is just so the user doesnt have
415 |         # to stare at a seemingly-frozen dialog
416 |         success = True
417 |     return success
418 | 


--------------------------------------------------------------------------------
/electrumpersonalserver/server/socks.py:
--------------------------------------------------------------------------------
  1 | """SocksiPy - Python SOCKS module.
  2 | Version 1.00
  3 | 
  4 | Copyright 2006 Dan-Haim. All rights reserved.
  5 | 
  6 | Redistribution and use in source and binary forms, with or without modification,
  7 | are permitted provided that the following conditions are met:
  8 | 1. Redistributions of source code must retain the above copyright notice, this
  9 |    list of conditions and the following disclaimer.
 10 | 2. Redistributions in binary form must reproduce the above copyright notice,
 11 |    this list of conditions and the following disclaimer in the documentation
 12 |    and/or other materials provided with the distribution.
 13 | 3. Neither the name of Dan Haim nor the names of his contributors may be used
 14 |    to endorse or promote products derived from this software without specific
 15 |    prior written permission.
 16 | 
 17 | THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
 18 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 19 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 20 | EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
 23 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 24 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 25 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
 26 | 
 27 | 
 28 | This module provides a standard socket-like interface for Python
 29 | for tunneling connections through SOCKS proxies.
 30 | 
 31 | """
 32 | 
 33 | import socket
 34 | import struct
 35 | import random
 36 | 
 37 | PROXY_TYPE_SOCKS4 = 1
 38 | PROXY_TYPE_SOCKS5 = 2
 39 | PROXY_TYPE_HTTP = 3
 40 | 
 41 | _defaultproxy = None
 42 | _orgsocket = socket.socket
 43 | 
 44 | 
 45 | class ProxyError(IOError):
 46 |     def __init__(self, value):
 47 |         self.value = value
 48 | 
 49 |     def __str__(self):
 50 |         return repr(self.value)
 51 | 
 52 | 
 53 | class GeneralProxyError(ProxyError):
 54 |     def __init__(self, value):
 55 |         self.value = value
 56 | 
 57 |     def __str__(self):
 58 |         return repr(self.value)
 59 | 
 60 | 
 61 | class Socks5AuthError(ProxyError):
 62 |     def __init__(self, value):
 63 |         self.value = value
 64 | 
 65 |     def __str__(self):
 66 |         return repr(self.value)
 67 | 
 68 | 
 69 | class Socks5Error(ProxyError):
 70 |     def __init__(self, value):
 71 |         self.value = value
 72 | 
 73 |     def __str__(self):
 74 |         return repr(self.value)
 75 | 
 76 | 
 77 | class Socks4Error(ProxyError):
 78 |     def __init__(self, value):
 79 |         self.value = value
 80 | 
 81 |     def __str__(self):
 82 |         return repr(self.value)
 83 | 
 84 | 
 85 | class HTTPError(ProxyError):
 86 |     def __init__(self, value):
 87 |         self.value = value
 88 | 
 89 |     def __str__(self):
 90 |         return repr(self.value)
 91 | 
 92 | 
 93 | _generalerrors = ("success", "invalid data", "not connected", "not available",
 94 |                   "bad proxy type", "bad input")
 95 | 
 96 | _socks5errors = ("succeeded", "general SOCKS server failure",
 97 |                  "connection not allowed by ruleset", "Network unreachable",
 98 |                  "Host unreachable", "Connection refused", "TTL expired",
 99 |                  "Command not supported", "Address type not supported",
100 |                  "Unknown error")
101 | 
102 | _socks5autherrors = ("succeeded", "authentication is required",
103 |                      "all offered authentication methods were rejected",
104 |                      "unknown username or invalid password", "unknown error")
105 | 
106 | _socks4errors = (
107 |     "request granted", "request rejected or failed",
108 |     "request rejected because SOCKS server cannot connect to identd on the client",
109 |     "request rejected because the client program and identd report different user-ids",
110 |     "unknown error")
111 | 
112 | 
113 | def setdefaultproxy(proxytype=None,
114 |                     addr=None,
115 |                     port=None,
116 |                     rdns=True,
117 |                     username=str(random.randrange(10000000, 99999999)),
118 |                     password=str(random.randrange(10000000, 99999999))):
119 |     """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
120 |     Sets a default proxy which all further socksocket objects will use,
121 |     unless explicitly changed.
122 |     """
123 |     global _defaultproxy
124 |     _defaultproxy = (proxytype, addr, port, rdns, username, password)
125 | 
126 | 
127 | class socksocket(socket.socket):
128 |     """socksocket([family[, type[, proto]]]) -> socket object
129 | 
130 |     Open a SOCKS enabled socket. The parameters are the same as
131 |     those of the standard socket init. In order for SOCKS to work,
132 |     you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
133 |     """
134 | 
135 |     def __init__(self,
136 |                  family=socket.AF_INET,
137 |                  type=socket.SOCK_STREAM,
138 |                  proto=0,
139 |                  _sock=None):
140 |         _orgsocket.__init__(self, family, type, proto, _sock)
141 |         if _defaultproxy is not None:
142 |             self.__proxy = _defaultproxy
143 |         else:
144 |             self.__proxy = (None, None, None, None, None, None)
145 |         self.__proxysockname = None
146 |         self.__proxypeername = None
147 | 
148 |     def __recvall(self, bytes):
149 |         """__recvall(bytes) -> data
150 |         Receive EXACTLY the number of bytes requested from the socket.
151 |         Blocks until the required number of bytes have been received.
152 |         """
153 |         data = b''
154 |         while len(data) < bytes:
155 |             data = data + self.recv(bytes - len(data))
156 |         return data
157 | 
158 |     def setproxy(self,
159 |                  proxytype=None,
160 |                  addr=None,
161 |                  port=None,
162 |                  rdns=True,
163 |                  username=None,
164 |                  password=None):
165 |         """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
166 |         Sets the proxy to be used.
167 |         proxytype - The type of the proxy to be used. Three types
168 |                 are supported: PROXY_TYPE_SOCKS4 (including socks4a),
169 |                 PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
170 |         addr -      The address of the server (IP or DNS).
171 |         port -      The port of the server. Defaults to 1080 for SOCKS
172 |                 servers and 8080 for HTTP proxy servers.
173 |         rdns -      Should DNS queries be preformed on the remote side
174 |                 (rather than the local side). The default is True.
175 |                 Note: This has no effect with SOCKS4 servers.
176 |         username -  Username to authenticate with to the server.
177 |                 The default is no authentication.
178 |         password -  Password to authenticate with to the server.
179 |                 Only relevant when username is also provided.
180 |         """
181 |         self.__proxy = (proxytype, addr, port, rdns, username, password)
182 | 
183 |     def __negotiatesocks5(self, destaddr, destport):
184 |         """__negotiatesocks5(self,destaddr,destport)
185 |         Negotiates a connection through a SOCKS5 server.
186 |         """
187 |         # First we'll send the authentication packages we support.
188 |         if (self.__proxy[4] is not None) and (self.__proxy[5] is not None):
189 |             # The username/password details were supplied to the
190 |             # setproxy method so we support the USERNAME/PASSWORD
191 |             # authentication (in addition to the standard none).
192 |             self.sendall(b'\x05\x02\x00\x02')
193 |         else:
194 |             # No username/password were entered, therefore we
195 |             # only support connections with no authentication.
196 |             self.sendall(b'\x05\x01\x00')
197 |         # We'll receive the server's response to determine which
198 |         # method was selected
199 |         chosenauth = self.__recvall(2)
200 |         if chosenauth[0:1] != b"\x05":
201 |             self.close()
202 |             raise GeneralProxyError((1, _generalerrors[1]))
203 |         # Check the chosen authentication method
204 |         if chosenauth[1:2] == b"\x00":
205 |             # No authentication is required
206 |             pass
207 |         elif chosenauth[1:2] == b"\x02":
208 |             # Okay, we need to perform a basic username/password
209 |             # authentication.
210 |             self.sendall(b'\x01' + bytes([len(self.__proxy[4])]) + self.__proxy[4].encode() +
211 |                          bytes([len(self.__proxy[5])]) + self.__proxy[5].encode())
212 |             authstat = self.__recvall(2)
213 |             if authstat[0:1] != b"\x01":
214 |                 # Bad response
215 |                 self.close()
216 |                 raise GeneralProxyError((1, _generalerrors[1]))
217 |             if authstat[1:2] != b"\x00":
218 |                 # Authentication failed
219 |                 self.close()
220 |                 raise Socks5AuthError((3, _socks5autherrors[3]))
221 |                 # Authentication succeeded
222 |         else:
223 |             # Reaching here is always bad
224 |             self.close()
225 |             if chosenauth[1:2] == b"\xFF":
226 |                 raise Socks5AuthError((2, _socks5autherrors[2]))
227 |             else:
228 |                 raise GeneralProxyError((1, _generalerrors[1]))
229 |         # Now we can request the actual connection
230 |         req = b"\x05\x01\x00"
231 |         # If the given destination address is an IP address, we'll
232 |         # use the IPv4 address request even if remote resolving was specified.
233 |         try:
234 |             ipaddr = socket.inet_aton(destaddr)
235 |             req = req + b"\x01" + ipaddr
236 |         except socket.error:
237 |             # Well it's not an IP number,  so it's probably a DNS name.
238 |             if self.__proxy[3]:
239 |                 # Resolve remotely
240 |                 ipaddr = None
241 |                 req = req + b"\x03" + bytes([len(destaddr)]) + destaddr.encode()
242 |             else:
243 |                 # Resolve locally
244 |                 ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
245 |                 req = req + b"\x01" + ipaddr
246 |         req += struct.pack(">H", destport)
247 |         self.sendall(req)
248 |         # Get the response
249 |         resp = self.__recvall(4)
250 |         if resp[0:1] != b"\x05":
251 |             self.close()
252 |             raise GeneralProxyError((1, _generalerrors[1]))
253 |         elif resp[1:2] != b"\x00":
254 |             # Connection failed
255 |             self.close()
256 |             raise Socks5Error(_socks5errors[min(9, ord(resp[1:2]))])
257 |         # Get the bound address/port
258 |         elif resp[3:4] == b"\x01":
259 |             boundaddr = self.__recvall(4)
260 |         elif resp[3:4] == b"\x03":
261 |             resp = resp + self.recv(1)
262 |             boundaddr = self.__recvall(resp[4:5])
263 |         else:
264 |             self.close()
265 |             raise GeneralProxyError((1, _generalerrors[1]))
266 |         boundport = struct.unpack(">H", self.__recvall(2))[0]
267 |         self.__proxysockname = (boundaddr, boundport)
268 |         if ipaddr is not None:
269 |             self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
270 |         else:
271 |             self.__proxypeername = (destaddr, destport)
272 | 
273 |     def getproxysockname(self):
274 |         """getsockname() -> address info
275 |         Returns the bound IP address and port number at the proxy.
276 |         """
277 |         return self.__proxysockname
278 | 
279 |     def getproxypeername(self):
280 |         """getproxypeername() -> address info
281 |         Returns the IP and port number of the proxy.
282 |         """
283 |         return _orgsocket.getpeername(self)
284 | 
285 |     def getpeername(self):
286 |         """getpeername() -> address info
287 |         Returns the IP address and port number of the destination
288 |         machine (note: getproxypeername returns the proxy)
289 |         """
290 |         return self.__proxypeername
291 | 
292 |     def __negotiatesocks4(self, destaddr, destport):
293 |         """__negotiatesocks4(self,destaddr,destport)
294 |         Negotiates a connection through a SOCKS4 server.
295 |         """
296 |         # Check if the destination address provided is an IP address
297 |         rmtrslv = False
298 |         try:
299 |             ipaddr = socket.inet_aton(destaddr)
300 |         except socket.error:
301 |             # It's a DNS name. Check where it should be resolved.
302 |             if self.__proxy[3]:
303 |                 ipaddr = b"\x00\x00\x00\x01"
304 |                 rmtrslv = True
305 |             else:
306 |                 ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
307 |         # Construct the request packet
308 |         req = b"\x04\x01" + struct.pack(">H", destport) + ipaddr
309 |         # The username parameter is considered userid for SOCKS4
310 |         if self.__proxy[4] is not None:
311 |             req = req + self.__proxy[4].encode()
312 |         req += b"\x00"
313 |         # DNS name if remote resolving is required
314 |         # NOTE: This is actually an extension to the SOCKS4 protocol
315 |         # called SOCKS4A and may not be supported in all cases.
316 |         if rmtrslv:
317 |             req = req + destaddr + b"\x00"
318 |         self.sendall(req)
319 |         # Get the response from the server
320 |         resp = self.__recvall(8)
321 |         if resp[0:1] != b"\x00":
322 |             # Bad data
323 |             self.close()
324 |             raise GeneralProxyError((1, _generalerrors[1]))
325 |         if resp[1:2] != b"\x5A":
326 |             # Server returned an error
327 |             self.close()
328 |             if ord(resp[1:2]) in (91, 92, 93):
329 |                 self.close()
330 |                 raise Socks4Error((ord(resp[1]), _socks4errors[ord(resp[1:2]) -
331 |                                                                90]))
332 |             else:
333 |                 raise Socks4Error((94, _socks4errors[4]))
334 |         # Get the bound address/port
335 |         self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(
336 |                 ">H", resp[2:4])[0])
337 |         if rmtrslv is not None:
338 |             self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
339 |         else:
340 |             self.__proxypeername = (destaddr, destport)
341 | 
342 |     def __negotiatehttp(self, destaddr, destport):
343 |         """__negotiatehttp(self,destaddr,destport)
344 |         Negotiates a connection through an HTTP server.
345 |         """
346 |         # If we need to resolve locally, we do this now
347 |         if not self.__proxy[3]:
348 |             addr = socket.gethostbyname(destaddr)
349 |         else:
350 |             addr = destaddr
351 |         self.sendall("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" +
352 |                      "Host: " + destaddr + "\r\n\r\n")
353 |         # We read the response until we get the string "\r\n\r\n"
354 |         resp = self.recv(1)
355 |         while resp.find(b"\r\n\r\n") == -1:
356 |             resp = resp + self.recv(1)
357 |         # We just need the first line to check if the connection
358 |         # was successful
359 |         statusline = resp.splitlines()[0].split(b" ", 2)
360 |         if statusline[0] not in ("HTTP/1.0", "HTTP/1.1"):
361 |             self.close()
362 |             raise GeneralProxyError((1, _generalerrors[1]))
363 |         try:
364 |             statuscode = int(statusline[1])
365 |         except ValueError:
366 |             self.close()
367 |             raise GeneralProxyError((1, _generalerrors[1]))
368 |         if statuscode != 200:
369 |             self.close()
370 |             raise HTTPError((statuscode, statusline[2]))
371 |         self.__proxysockname = ("0.0.0.0", 0)
372 |         self.__proxypeername = (addr, destport)
373 | 
374 |     def connect(self, destpair):
375 |         """connect(self,despair)
376 |         Connects to the specified destination through a proxy.
377 |         destpar - A tuple of the IP/DNS address and the port number.
378 |         (identical to socket's connect).
379 |         To select the proxy server use setproxy().
380 |         """
381 |         # Do a minimal input check first
382 |         if (type(destpair) in
383 |                 (list, tuple) == False) or (len(destpair) < 2) or (
384 |                     type(destpair[0]) != str) or (type(destpair[1]) != int):
385 |             raise GeneralProxyError((5, _generalerrors[5]))
386 |         if self.__proxy[0] == PROXY_TYPE_SOCKS5:
387 |             if self.__proxy[2] is not None:
388 |                 portnum = self.__proxy[2]
389 |             else:
390 |                 portnum = 1080
391 |             _orgsocket.connect(self, (self.__proxy[1], portnum))
392 |             self.__negotiatesocks5(destpair[0], destpair[1])
393 |         elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
394 |             if self.__proxy[2] is not None:
395 |                 portnum = self.__proxy[2]
396 |             else:
397 |                 portnum = 1080
398 |             _orgsocket.connect(self, (self.__proxy[1], portnum))
399 |             self.__negotiatesocks4(destpair[0], destpair[1])
400 |         elif self.__proxy[0] == PROXY_TYPE_HTTP:
401 |             if self.__proxy[2] is not None:
402 |                 portnum = self.__proxy[2]
403 |             else:
404 |                 portnum = 8080
405 |             _orgsocket.connect(self, (self.__proxy[1], portnum))
406 |             self.__negotiatehttp(destpair[0], destpair[1])
407 |         elif self.__proxy[0] is None:
408 |             _orgsocket.connect(self, (destpair[0], destpair[1]))
409 |         else:
410 |             raise GeneralProxyError((4, _generalerrors[4]))
411 | 


--------------------------------------------------------------------------------
/release-notes:
--------------------------------------------------------------------------------
  1 | # Release v0.2.4 (16th June 2022)
  2 | 
  3 | New release, thanks to contributions by andrewtoth and theStack
  4 | And thanks to everyone else who contributed via discussion and donations
  5 | 
  6 | * Fixed crash caused by deprecated RPC in Bitcoin Core 23.0
  7 | * Added signet support
  8 | 
  9 | # Release v0.2.3 (9th March 2022)
 10 | 
 11 | New release, thanks to contributions by andrewtoth and federicociro
 12 | And thanks to everyone else who contributed via discussion and donations
 13 | 
 14 | * Fixed crash caused by deprecated RPC in Bitcoin Core 22.0
 15 | 
 16 | # Release v0.2.2 (29th May 2021)
 17 | 
 18 | New release, thanks to contributions by Liongrass, Talkless and parazyd
 19 | And thanks to everyone else who contributed via discussion and donations
 20 | 
 21 | * Rewrite mempool handling to always be responsive. Previously the server
 22 |   would massively lag if the mempool was large, so most users just disabled
 23 |   the mempool histogram. With this update that is no longer an issue.
 24 | * If Electrum Personal Server fails at startup it will now return a non-zero
 25 |   error code, making it more usable with automated scripts like systemd.
 26 | * Client will now warn user if tor broadcasting fails because tor is not
 27 |   accessible.
 28 | * Various optimizations and bug fixes.
 29 | 
 30 | # Release v0.2.1.1 (9th June 2020)
 31 | 
 32 | Bugfix release. Thanks to everyone who reported the bug and jmacxx who wrote
 33 | the pull request to fix it. And thanks to everyone who contributed in general
 34 | to electrum personal server.
 35 | 
 36 | * Fixed bug where the server would crash in certain conditions. Instead the
 37 |   server should have caught the error and continued.
 38 | 
 39 | # Release v0.2.1 (4th June 2020)
 40 | 
 41 | New release, thanks to contributions by DriftwoodPalace, m-schmoock and wiredcheetah
 42 | And thanks to everyone else who contributed via discussion and donations
 43 | 
 44 | * Massive speedup to startup time and initial import of addresses. This is done
 45 |   using the descriptor wallets feature of Bitcoin Core 0.20. The speedup is
 46 |   very helpful when running Electrum Personal Server on low powered devices
 47 |   such as the raspberry pi
 48 | * Close the connection to client if it requests an unknown address or if the
 49 |   connection to the Bitcoin node is lost. The user will see a red dot in
 50 |   Electrum indicating that something is wrong which should prompt them to fix.
 51 | * Increase default polling interval to make the server more responsive to new
 52 |   transactions and confirmations
 53 | * Reduce spam in the debug log and info log
 54 | * Various other tweaks and bug fixes
 55 | 
 56 | # Release v0.2.0 (5th December 2019)
 57 | 
 58 | New release, thanks to code contributions by suvayu, andrewtoth and Sosthene00
 59 | And thanks to everyone else who contributed via discussion and donations
 60 | 
 61 | * Implemented tor broadcasting of transactions, which happens by default if tor
 62 |   is running on the same machine.
 63 | * Also check that the last address of each master public key has been imported,
 64 |   along with the first three.
 65 | * Add bandwidth usage per day and blockchain size to the server banner
 66 | * Support using `vsize` instead of `size` for the mempool calculation, which is
 67 |   the correct behaviour for Bitcoin Core 0.19
 68 | * Allow rescan date to also be passed via CLI args. Wait for any rescanning to
 69 |   finish on startup. This allows Electrum Personal Server to be more easily
 70 |   used with scripting.
 71 | * Various other bugfixes
 72 | 
 73 | # Release v0.1.7 (26th April 2019)
 74 | 
 75 | New release, thanks to code contributions by suvayu and andrewtoth
 76 | And thanks to everyone else who contributed via discussion and donations
 77 | 
 78 | * If pruning is enabled and block is not available then send dummy merkle
 79 |   proof, which Electrum will accept if run with the command line
 80 |   flag --skipmerklecheck
 81 | * Added option to allow broadcasting unconfirmed transactions via any
 82 |   system call, for example it could be a shell script which broadcasts
 83 |   via SMS or radio.
 84 | * Added option which allows disabling the mempool histogram feature
 85 |   which is useful on low-powered devices when the mempool is large.
 86 | * Deprecated electrum-personal-server-rescan script in favour of
 87 |   electrum-personal-server --rescan
 88 | * Releases will now also be packaged as windows binaries using pyinstaller.
 89 | * No longer adds orphaned coinbase txes as unconfirmed.
 90 | * Fix bug involving transactions with unconfirmed inputs.
 91 | * Various other bugfixes
 92 | 
 93 | # Release v0.1.6 - (15th November 2018)
 94 | 
 95 | New release, thanks to code contributions by suvayu and andrewtoth
 96 | And thanks to everyone else who contributed to issues and discussion
 97 | 
 98 | * Made installable with pip, thanks to suvayu
 99 | * Fix bug where coinbase transactions would be ignored, thanks to andrewtoth
100 | * Support Electrum protocol version 1.4
101 | * Support blockchain.transaction.id_from_pos which is necessary for
102 |   Lightning support in Electrum
103 | * Increase default initial_import_count to 1000
104 | * Added or clarified various error and info messages
105 | * Disabled transaction broadcasting when blocksonly is enabled for privacy
106 | * Fixed various small bugs
107 | 
108 | 
109 | # Release v0.1.5 - (7th September 2018)
110 | 
111 | Bugfix release
112 | 
113 | * Fixed crash bug caused by behavour of getaddressesbylabel
114 | 
115 | 
116 | # Release v0.1.4 - (5th September 2018)
117 | 
118 | * Used 127.0.0.1 instead of localhost to help improve windows support
119 | * Fixed crash bug if the client requests an out-of-range header
120 | * Supported Bitcoin Core 0.17 which deprecates accounts
121 | 
122 | 
123 | # Release v0.1.3 - (4th July 2018)
124 | 
125 | Bugfix release, mainly to correctly support Electrum 3.2
126 | 
127 | * Added support for raw block headers
128 | * Implemented protocol method `blockchain.block.headers`
129 | * Make the address status of a empty address be None
130 | * Fixed bug involving rare situation where the result of the listtransactions
131 |   RPC call did not have an `address` field
132 | 
133 | 
134 | # Release v0.1.2 - (30th June 2018)
135 | 
136 | * Added support for mempool histogram feature
137 | * Handles conflicted transactions, for when a chain reorg happens
138 | * Added IP address whitelisting feature
139 | * Bugfix when Electrum requests block headers out of range
140 | * Bugfix when listtransactions has more than 1000 entries
141 | * Added many more tests, which now use py.test
142 | * Added regtest support
143 | 
144 | 
145 | # Release v0.1.1 - (1st April 2018)
146 | 
147 | Bugfix release, thanks to
148 | 
149 | * Added option to manually configure rpc_user and rpc_password, instead of using
150 |   the .cookie file.
151 | * Made json-rpc error messages have more detail.
152 | * Added method for user to configure Electrum Personal Server's current working
153 |   directory, which is useful for running it from systemd or another automated
154 |   tool.
155 | * Updated readme file to add information that tripped people up.
156 | * Now handles conflicted transactions.
157 | 
158 | 
159 | # Beta release v0.1 - (29th Mar 2018)
160 | 
161 | Released first beta version.
162 | 
163 | * Merkle proofs supported using bitcoind's `gettxoutproof` RPC call.
164 | * Deterministic wallets implemented which support all Electrum master public
165 |   key formats.
166 | * Created rescan script which allows rescanning from a given block height
167 |   instead of scanning the entire blockchain. Also allows the user to input a
168 |   dd/mm/yyyy timestamp, which is converted to a block height (with 2 weeks
169 |   safety) to rescan from there.
170 | * Automated tests created for merkle proofs, deterministic wallets and
171 |   monitoring of transactions.
172 | * SSL server socket is used and a default SSL certificate is included in the
173 |   repository, which users can replace with their own.
174 | * No longer depends on pybitcointools' transaction code. That package is only
175 |   used for bip32 support for deterministic wallets. Bech32 addresses now
176 |   supported.
177 | * RPC auth details can be obtained from the .cookie file.
178 | * Bitcoin Core's multi-wallet feature supported.
179 | 
180 | 
181 | # Alpha release - (8th Feb 2018)
182 | 
183 | Released first alpha version which builds address history from bitcoin'd
184 | wallet, monitors addresses for new transactions and accepts connections from
185 | Electrum wallet.
186 | 
187 | 


--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [aliases]
2 | test=pytest
3 | 
4 | [tool:pytest]
5 | addopts = --pdb
6 | 


--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
 1 | from setuptools import setup, find_packages
 2 | 
 3 | setup(
 4 |     name="electrum-personal-server",
 5 |     version="0.2.4",
 6 |     description="Electrum Personal Server",
 7 |     author="Chris Belcher",
 8 |     license="MIT",
 9 |     include_package_data=True,
10 |     packages=find_packages(exclude=["tests"]),
11 |     tests_require=["pytest"],
12 |     entry_points={
13 |         "console_scripts": [
14 |             "electrum-personal-server = electrumpersonalserver.server.common:main",
15 |         ]
16 |     },
17 |     package_data={"electrumpersonalserver": ["certs/*"]},
18 |     data_files=[
19 |         ("share/doc/electrum-personal-server", ["README.md",
20 |                                                 "config.ini_sample"]),
21 |     ],
22 | )
23 | 


--------------------------------------------------------------------------------
/test/test_electrum_protocol.py:
--------------------------------------------------------------------------------
  1 | 
  2 | import pytest
  3 | import logging
  4 | import json
  5 | 
  6 | from electrumpersonalserver.server import (
  7 |     TransactionMonitor,
  8 |     JsonRpcError,
  9 |     ElectrumProtocol,
 10 |     get_block_header,
 11 |     get_current_header,
 12 |     get_block_headers_hex,
 13 |     JsonRpcError,
 14 |     get_status_electrum
 15 | )
 16 | 
 17 | logger = logging.getLogger('ELECTRUMPERSONALSERVER-TEST')
 18 | logger.setLevel(logging.DEBUG)
 19 | 
 20 | DUMMY_JSONRPC_BLOCKCHAIN_HEIGHT = 100000
 21 | 
 22 | def get_dummy_hash_from_height(height):
 23 |     if height == 0:
 24 |         return "00"*32
 25 |     return str(height) + "a"*(64 - len(str(height)))
 26 | 
 27 | def get_height_from_dummy_hash(hhash):
 28 |     if hhash == "00"*32:
 29 |         return 0
 30 |     return int(hhash[:hhash.index("a")])
 31 | 
 32 | class DummyJsonRpc(object):
 33 |     def __init__(self):
 34 |         self.calls = {}
 35 |         self.blockchain_height = DUMMY_JSONRPC_BLOCKCHAIN_HEIGHT
 36 | 
 37 |     def call(self, method, params):
 38 |         if method not in self.calls:
 39 |             self.calls[method] = [0, []]
 40 |         self.calls[method][0] += 1
 41 |         self.calls[method][1].append(params)
 42 |         if method == "getbestblockhash":
 43 |             return get_dummy_hash_from_height(self.blockchain_height)
 44 |         elif method == "getblockhash":
 45 |             height = params[0]
 46 |             if height > self.blockchain_height:
 47 |                 raise JsonRpcError()
 48 |             return get_dummy_hash_from_height(height)
 49 |         elif method == "getblockheader":
 50 |             blockhash = params[0]
 51 |             height = get_height_from_dummy_hash(blockhash)
 52 |             header = {
 53 |                 "hash": blockhash,
 54 |                 "confirmations": self.blockchain_height - height + 1,
 55 |                 "height": height,
 56 |                 "version": 536870912,
 57 |                 "versionHex": "20000000",
 58 |                 "merkleroot": "aa"*32,
 59 |                 "time": height*100,
 60 |                 "mediantime": height*100,
 61 |                 "nonce": 1,
 62 |                 "bits": "207fffff",
 63 |                 "difficulty": 4.656542373906925e-10,
 64 |                 "chainwork": "000000000000000000000000000000000000000000000"
 65 |                     + "00000000000000000da",
 66 |                 "nTx": 1,
 67 |             }
 68 |             if height > 1:
 69 |                 header["previousblockhash"] = get_dummy_hash_from_height(
 70 |                     height - 1)
 71 |             elif height == 1:
 72 |                 header["previousblockhash"] = "00"*32 #genesis block
 73 |             elif height == 0:
 74 |                 pass #no prevblock for genesis
 75 |             else:
 76 |                 assert 0
 77 |             if height < self.blockchain_height:
 78 |                 header["nextblockhash"] = get_dummy_hash_from_height(height + 1)
 79 |             return header
 80 |         elif method == "gettransaction":
 81 |             for t in self.txlist:
 82 |                 if t["txid"] == params[0]:
 83 |                     return t
 84 |             raise JsonRpcError()
 85 |         else:
 86 |             raise ValueError("unknown method in dummy jsonrpc")
 87 | 
 88 | def test_get_block_header():
 89 |     rpc = DummyJsonRpc()
 90 |     for height in [0, 1000]:
 91 |         for raw in [True, False]:
 92 |             blockhash = rpc.call("getblockhash", [height])
 93 |             ret = get_block_header(rpc, blockhash, raw)
 94 |             if raw:
 95 |                 assert type(ret) == dict
 96 |                 assert "hex" in ret
 97 |                 assert "height" in ret
 98 |                 assert len(ret["hex"]) == 160
 99 |             else:
100 |                 assert type(ret) == dict
101 |                 assert len(ret) == 7
102 | 
103 | def test_get_current_header():
104 |     rpc = DummyJsonRpc()
105 |     for raw in [True, False]:
106 |         ret = get_current_header(rpc, raw)
107 |         assert type(ret[0]) == str
108 |         assert len(ret[0]) == 64
109 |         if raw:
110 |             assert type(ret[1]) == dict
111 |             assert "hex" in ret[1]
112 |             assert "height" in ret[1]
113 |             assert len(ret[1]["hex"]) == 160
114 |         else:
115 |             assert type(ret[1]) == dict
116 |             assert len(ret[1]) == 7
117 | 
118 | @pytest.mark.parametrize(
119 |     "start_height, count",
120 |     [(100, 200),
121 |     (DUMMY_JSONRPC_BLOCKCHAIN_HEIGHT + 10, 5),
122 |     (DUMMY_JSONRPC_BLOCKCHAIN_HEIGHT - 10, 15),
123 |     (0, 250)
124 |     ]
125 | )
126 | def test_get_block_headers_hex(start_height, count):
127 |     rpc = DummyJsonRpc()
128 |     ret = get_block_headers_hex(rpc, start_height, count)
129 |     print("start_height=" + str(start_height) + " count=" + str(count))
130 |     assert len(ret) == 2
131 |     available_blocks = -min(0, start_height - DUMMY_JSONRPC_BLOCKCHAIN_HEIGHT
132 |         - 1)
133 |     expected_count = min(available_blocks, count)
134 |     assert len(ret[0]) == expected_count*80*2 #80 bytes/header, 2 chars/byte
135 |     assert ret[1] == expected_count
136 | 
137 | @pytest.mark.parametrize(
138 |     "invalid_json_query",
139 |     [
140 |         {"valid-json-no-method": 5}
141 |     ]
142 | ) 
143 | def test_invalid_json_query_line(invalid_json_query):
144 |     protocol = ElectrumProtocol(None, None, logger, None, None, None)
145 |     with pytest.raises(IOError) as e:
146 |         protocol.handle_query(invalid_json_query)
147 | 
148 | def create_electrum_protocol_instance(broadcast_method="own-node",
149 |         tor_hostport=("127.0.0.1", 9050),
150 |         disable_mempool_fee_histogram=False):
151 |     protocol = ElectrumProtocol(DummyJsonRpc(), DummyTransactionMonitor(),
152 |         logger, broadcast_method, tor_hostport, disable_mempool_fee_histogram)
153 |     sent_replies = []
154 |     protocol.set_send_reply_fun(lambda l: sent_replies.append(l))
155 |     assert len(sent_replies) == 0
156 |     return protocol, sent_replies
157 | 
158 | def dummy_script_hash_to_history(scrhash):
159 |     index = int(scrhash[:scrhash.index("s")])
160 |     tx_count = (index+2) % 5
161 |     height = 500
162 |     return [(index_to_dummy_txid(i), height) for i in range(tx_count)]
163 | 
164 | def index_to_dummy_script_hash(index):
165 |     return str(index) + "s"*(64 - len(str(index)))
166 | 
167 | def index_to_dummy_txid(index):
168 |     return str(index) + "t"*(64 - len(str(index)))
169 | 
170 | def dummy_txid_to_dummy_tx(txid):
171 |     return txid[::-1] * 6
172 | 
173 | class DummyTransactionMonitor(object):
174 |     def __init__(self):
175 |         self.deterministic_wallets = list(range(5))
176 |         self.address_history = list(range(5))
177 |         self.subscribed_addresses = []
178 |         self.history_hashes = {}
179 | 
180 |     def get_electrum_history_hash(self, scrhash):
181 |         history = dummy_script_hash_to_history(scrhash)
182 |         hhash = get_status_electrum(history)
183 |         self.history_hashes[scrhash] = history
184 |         return hhash
185 | 
186 |     def get_electrum_history(self, scrhash):
187 |         return self.history_hashes[scrhash]
188 | 
189 |     def unsubscribe_all_addresses(self):
190 |         self.subscribed_addresses = []
191 | 
192 |     def subscribe_address(self, scrhash):
193 |         self.subscribed_addresses.append(scrhash)
194 |         return True
195 | 
196 |     def get_address_balance(self, scrhash):
197 |         pass
198 | 
199 | def test_script_hash_sync():
200 |     protocol, sent_replies = create_electrum_protocol_instance()
201 |     scrhash_index = 0
202 |     scrhash = index_to_dummy_script_hash(scrhash_index)
203 |     protocol.handle_query({"method": "blockchain.scripthash.subscribe",
204 |         "params": [scrhash], "id": 0})
205 |     assert len(sent_replies) == 1
206 |     assert len(protocol.txmonitor.subscribed_addresses) == 1
207 |     assert protocol.txmonitor.subscribed_addresses[0] == scrhash
208 |     assert len(sent_replies) == 1
209 |     assert len(sent_replies[0]["result"]) == 64
210 |     history_hash = sent_replies[0]["result"]
211 | 
212 |     protocol.handle_query({"method": "blockchain.scripthash.get_history",
213 |         "params": [scrhash], "id": 0})
214 |     assert len(sent_replies) == 2
215 |     assert get_status_electrum(sent_replies[1]["result"]) == history_hash
216 | 
217 |     #updated scripthash but actually nothing changed, history_hash unchanged
218 |     protocol.on_updated_scripthashes([scrhash])
219 |     assert len(sent_replies) == 3
220 |     assert sent_replies[2]["method"] == "blockchain.scripthash.subscribe"
221 |     assert sent_replies[2]["params"][0] == scrhash
222 |     assert sent_replies[2]["params"][1] == history_hash
223 | 
224 |     protocol.on_disconnect()
225 |     assert len(protocol.txmonitor.subscribed_addresses) == 0
226 | 
227 | def test_headers_subscribe():
228 |     protocol, sent_replies = create_electrum_protocol_instance()
229 | 
230 |     protocol.handle_query({"method": "server.version", "params": ["test-code",
231 |         1.4], "id": 0}) #protocol version of 1.4 means only raw headers used
232 |     assert len(sent_replies) == 1
233 | 
234 |     protocol.handle_query({"method": "blockchain.headers.subscribe", "params":
235 |         [], "id": 0})
236 |     assert len(sent_replies) == 2
237 |     assert "height" in sent_replies[1]["result"]
238 |     assert sent_replies[1]["result"]["height"] == protocol.rpc.blockchain_height
239 |     assert "hex" in sent_replies[1]["result"]
240 |     assert len(sent_replies[1]["result"]["hex"]) == 80*2 #80 b/header, 2 b/char
241 | 
242 |     protocol.rpc.blockchain_height += 1
243 |     new_bestblockhash, header = get_current_header(protocol.rpc,
244 |         protocol.are_headers_raw)
245 |     protocol.on_blockchain_tip_updated(header)
246 |     assert len(sent_replies) == 3
247 |     assert "method" in sent_replies[2]
248 |     assert sent_replies[2]["method"] == "blockchain.headers.subscribe"
249 |     assert "params" in sent_replies[2]
250 |     assert "height" in sent_replies[2]["params"][0]
251 |     assert sent_replies[2]["params"][0]["height"]\
252 |         == protocol.rpc.blockchain_height
253 |     assert "hex" in sent_replies[2]["params"][0]
254 |     assert len(sent_replies[2]["params"][0]["hex"]) == 80*2 #80 b/header, 2 b/c
255 | 
256 | def test_server_ping():
257 |     protocol, sent_replies = create_electrum_protocol_instance()
258 |     idd = 1
259 |     protocol.handle_query({"method": "server.ping", "id": idd})
260 |     assert len(sent_replies) == 1
261 |     assert sent_replies[0]["result"] == None
262 |     assert sent_replies[0]["id"] == idd
263 | 
264 | #test scripthash.subscribe, scripthash.get_history transaction.get
265 | # transaction.get_merkle
266 | 
267 | 


--------------------------------------------------------------------------------
/test/test_merkleproof.py:
--------------------------------------------------------------------------------
  1 | 
  2 | import pytest
  3 | 
  4 | from electrumpersonalserver.server import (
  5 |     convert_core_to_electrum_merkle_proof,
  6 |     hash_merkle_root
  7 | )
  8 | 
  9 | '''
 10 | # code for printing out proofs not longer than 80 per line
 11 | proof = ""
 12 | def chunks(d, n):
 13 |     return [d[x:x + n] for x in range(0, len(d), n)]
 14 | print("\" + \n\"".join(chunks(proof, 71)))
 15 | '''
 16 | 
 17 | @pytest.mark.parametrize(
 18 |     "proof",
 19 |     [
 20 |     #txcount 819, pos 5
 21 |     "0300000026e696fba00f0a43907239305eed9e55824e0e376636380f000000000000000" + 
 22 |     "04f8a2ce51d6c69988029837688cbfc2f580799fa1747456b9c80ab808c1431acd0b07f" + 
 23 |     "5543201618cadcfbf7330300000b0ff1e0050fed22ca360e0935e053b0fe098f6f9e090" + 
 24 |     "f5631013361620d964fe2fd88544ae10b40621e1cd24bb4306e3815dc237f77118a45d7" + 
 25 |     "5ada9ee362314b70573732bce59615a3bcc1bbacd04b33b7819198212216b5d62d75be5" + 
 26 |     "9221ada17ba4fb2476b689cccd3be54732fd5630832a94f11fa3f0dafd6f904d43219e0" + 
 27 |     "d7de110158446b5b598bd241f7b5df4da0ebc7d30e7748d487917b718df51c681174e6a" + 
 28 |     "bab8042cc7c1c436221c098f06a56134f9247a812126d675d69c82ba1c715cfc0cde462" + 
 29 |     "fd1fbe5dc87f6b8db2b9c060fcd59a20e7fe8e921c3676937a873ff88684f4be4d015f2" + 
 30 |     "4f26af6d2cf78335e9218bcceba4507d0b4ba6cb933aa01ef77ae5eb411893ec0f74b69" + 
 31 |     "590fb0f5118ac937c02ccd47e9d90be78becd11ecf854d7d268eeb479b74d137278c0a5" + 
 32 |     "017d29e90cd5b35a4680201824fb0eb4f404e20dfeaec4d50549030b7e7e220b02eb210" + 
 33 |     "5f3d2e8bcc94d547214a9d03ff1600",
 34 |     #txcount 47, pos 9
 35 |     "0100000053696a625fbd16df418575bce0c4148886c422774fca5fcab80100000000000" + 
 36 |     "01532bfe4f9c4f56cd141028e5b59384c133740174b74b1982c7f01020b90ce05577c67" + 
 37 |     "508bdb051a7ec2ef942f000000076cde2eb7efa90b36d48aed612e559ff2ba638d8d400" + 
 38 |     "b14b0c58df00c6a6c33b65dc8fa02f4ca56e1f4dcf17186fa9bbd990ce150b6e2dc9e9e" + 
 39 |     "56bb4f270fe56fde6bdd73a7a7e82767714862888e6b759568fb117674ad23050e29311" + 
 40 |     "97494d457efb72efdb9cb79cd4a435724908a0eb31ec7f7a67ee03837319e098b43edad" + 
 41 |     "3be9af75ae7b30db6f4f93ba0fdd941fdf70fe8cc38982e03bd292f5bd02f28137d343f" + 
 42 |     "908c7d6417379afe8349a257af3ca1f74f623be6a416fe1aa96a8f259983f2cf32121bc" + 
 43 |     "e203955a378b3b44f132ea6ab94c7829a6c3b360c9f8da8e74027701",
 44 |     #txcount 2582, pos 330
 45 |     "000000206365d5e1d8b7fdf0b846cfa902115c1b8ced9dd49cb17800000000000000000" + 
 46 |     "01032e829e1f9a5a09d0492f9cd3ec0762b7facea555989c3927d3d975fd4078c771849" + 
 47 |     "5a45960018edd3b9e0160a00000dfe856a7d5d77c23ebf85c68b5eb303d85e56491ed6d" + 
 48 |     "204372625d0b4383df5a44d6e46d2db09d936b9f5d0b53e0dbcb3efb7773d457369c228" + 
 49 |     "fd1ce6e11645e366a58b3fc1e8a7c916710ce29a87265a6729a3b221b47ea9c8e6f4870" + 
 50 |     "7b112b8d67e5cfb3db5f88b042dc49e4e5bc2e61c28e1e0fbcba4c741bb5c75cac58ca0" + 
 51 |     "4161a7377d70f3fd19a3e248ed918c91709b49afd3760f89ed2fefbcc9c23447ccb40a2" + 
 52 |     "be7aba22b07189b0bf90c62db48a9fe37227e12c7af8c1d4c22f9f223530dacdd5f3ad8" + 
 53 |     "50ad4badf16cc24049a65334f59bf28c15cecda1a4cf3f2937bd70ee84f66569ce8ef95" + 
 54 |     "1d50cca46d60337e6c697685b38ad217967bbe6801d03c44fcb808cd035be31888380a2" + 
 55 |     "df1be14b6ff100de83cab0dce250e2b40ca3b47e8309f848646bee63b6185c176d84f15" + 
 56 |     "46a482e7a65a87d1a2d0d5a2b683e2cae0520df1e3525a71d71e1f551abd7d238c3bcb4" + 
 57 |     "ecaeea7d5988745fa421a8604a99857426957a2ccfa7cd8df145aa8293701989dd20750" + 
 58 |     "5923fcb339843944ce3d21dc259bcda9c251ed90d4e55af2cf5b15432050084f513ac74" + 
 59 |     "c0bdd4b6046fb70100",
 60 |     #txcount 2861, pos 2860, last tx with many duplicated nodes down the tree
 61 |     "00000020c656c90b521a2bbca14174f2939b882a28d23d86144b0e00000000000000000" + 
 62 |     "0cf5185a8e369c3de5f15e039e777760994fd66184b619d839dace3aec9953fd6d86159" + 
 63 |     "5ac1910018ee097a972d0b0000078d20d71c3114dbf52bb13f2c18f987891e8854d2d29" + 
 64 |     "f61c0b3d3256afcef7c0b1e6f76d6431f93390ebd28dbb81ad7c8f08459e85efeb23cc7" + 
 65 |     "2df2c5612215bf53dd4ab3703886bc8c82cb78ba761855e495fb5dc371cd8fe25ae974b" + 
 66 |     "df42269e267caf898a9f34cbf2350eaaa4afbeaea70636b5a3b73682186817db5b33290" + 
 67 |     "bd5c696bd8d0322671ff70c5447fcd7bdc127e5b84350c6b14b5a3b86b424d7db38d39f" + 
 68 |     "171f57e255a31c6c53415e3d65408b6519a40aacc49cad8e70646d4cb0d23d4a63068e6" + 
 69 |     "c220efc8a2781e9e774efdd334108d7453043bd3c8070d0e5903ad5b07",
 70 |     #txcount 7, pos 6, duplicated entry in the last depth, at tx level
 71 |     "0100000056e02c6d3278c754e0699517834741f7c4ad3dcbfeb7803a346200000000000" + 
 72 |     "0af3bdd5dd465443fd003e9281455e60aae573dd4d46304d7ba17276ea33d506488cbb4" + 
 73 |     "4dacb5001b9ebb193b0700000003cd3abb2eb7583165f36b56add7268be9027ead4cc8f" + 
 74 |     "888ec650d3b1c1f4de28a0ff7c8b463b2042d09598f0e5e5905de362aa1cf75252adc22" + 
 75 |     "719b8e1bc969adcfbc4782b8eafc9352263770b91a0f189ae051cbe0e26046c2b14cf3d" + 
 76 |     "8be0bc40135",
 77 |     #txcount 6, pos 5, duplicated entry in the last-but-one depth
 78 |     "01000000299edfd28524eae4fb6012e4087afdb6e1b912db85e612374b0300000000000" + 
 79 |     "0e16572394f8578a47bf36e15cd16faa5e3b9e18805cf4e271ae4ef95aa8cea7eb31fa1" + 
 80 |     "4e4b6d0b1a42857d960600000003f52b45ed953924366dab3e92707145c78615b639751" + 
 81 |     "ecb7be1c5ecc09b592ed588ca0e15a89e9049a2dbcadf4d8362bd1f74a6972f176617b5" + 
 82 |     "8a5466c8a4121fc3e2d6fa66c8637b387ef190ab46d6e9c9dae4bbccd871c72372b3dbc" + 
 83 |     "6edefea012d",
 84 |     #txcount 5, pos 4, duplicated on the last and second last depth
 85 |     "010000004d891de57908d89e9e5585648978d7650adc67e856c2d8c18c1800000000000" + 
 86 |     "04746fd317bffecd4ffb320239caa06685bafe0c1b5463b24d636e45788796657843d1b" + 
 87 |     "4d4c86041be68355c40500000002d8e26c89c46477f2407d866d2badbd98e43e732a670" + 
 88 |     "e96001faf1744b27e5fdd018733d72e31a2d6a0d94f2a3b35fcc66fb110c40c5bbff82b" + 
 89 |     "f87606553d541d011d",
 90 |     #txcount 2739, pos 0, coinbase tx
 91 |     "000000209f283da030c6e6d0ff5087a87c430d140ed6b4564fa34d00000000000000000" + 
 92 |     "0ec1513723e3652c6b8e777c41eb267ad8dd2025e85228840f5cfca7ffe1fb331afff8a" + 
 93 |     "5af8e961175e0f7691b30a00000df403e21a4751fbd52457f535378ac2dcf111199e9ea" + 
 94 |     "6f78f6c2663cb99b58203438d8f3b26f7f2804668c1df7d394a4726363d4873b2d85b71" + 
 95 |     "2e44cf4f5e4f33f22a8f3a1672846bd7c4570c668e6ee12befda23bfa3d0fcd30b1b079" + 
 96 |     "19b01c40b1e31b6d34fcdbb99539d46eb97a3ae15386f1ab0f28ecacadd9fc3fa4ce49a" + 
 97 |     "1a1839d815229f54036c8a3035d91e80e8dc127b62032b4e652550b4fc0aee0f6e85a14" + 
 98 |     "307d85ed9dde62acff9a0f7e3b52370a10d6c83ec13a0b4a8fafe87af368a167d7e9b63" + 
 99 |     "3b84b6ea65f1ce5e8ccc1840be0a4dab0099e25afccc7f2fdbda54cd65ecbac8d9a550c" + 
100 |     "108b4e18d3af59129d373fde4c80848858fd6f7fc1e27387a38833473ca8a47729fa6e1" + 
101 |     "cc14b584c14dad768108ff18cc6acdc9c31d32dc71c3c80856664a3fff870fe419a59aa" + 
102 |     "9033356590475d36086f0b3c0ece34c0f3756675c610fb980ff3363af6f9c0918a7c677" + 
103 |     "23371849de9c1026515c2900a80b3aee4f2625c8f48cd5eb967560ee8ebe58a8d41c331" + 
104 |     "f6d5199795735d4f0494bdf592d166fa291062733619f0f133605087365639de2d9d5d6" + 
105 |     "921f4b4204ff1f0000",
106 |     #txcount 1, pos 0, coinbase tx in an empty block, tree with height 1
107 |     "010000000508085c47cc849eb80ea905cc7800a3be674ffc57263cf210c59d8d0000000" + 
108 |     "0112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98173ac9799a852fa39add320cd66" + 
109 |     "49ffff001d1e2de5650100000001112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98" + 
110 |     "173ac9799a852fa39add30101",
111 |     #txcount 2, pos 1, tree with height 2
112 |     "010000004e24a2880cd72d9bde7502087bd3756819794dc7548f68dd68dc30010000000" + 
113 |     "02793fce9cdf91b4f84760571bf6009d5f0ffaddbfdc9234ef58a036096092117b10f4b" + 
114 |     "4cfd68011c903e350b0200000002ee50562fc6f995eff2df61be0d5f943bac941149aa2" + 
115 |     "1aacb32adc130c0f17d6a2077a642b1eabbc5120e31566a11e2689aa4d39b01cce9a190" + 
116 |     "2360baa5e4328e0105"
117 |     ]
118 | )
119 | 
120 | def test_merkleproof(proof):
121 |     try:
122 |         electrum_proof = convert_core_to_electrum_merkle_proof(proof)
123 |         #print(electrum_proof)
124 |         implied_merkle_root = hash_merkle_root(
125 |             electrum_proof["merkle"], electrum_proof["txid"],
126 |             electrum_proof["pos"])
127 |         assert implied_merkle_root == electrum_proof["merkleroot"]
128 |     except ValueError:
129 |         import traceback
130 |         traceback.print_exc()
131 |         assert 0
132 | 
133 | 


--------------------------------------------------------------------------------
/test/test_parse_mpks.py:
--------------------------------------------------------------------------------
 1 | 
 2 | import pytest
 3 | 
 4 | from electrumpersonalserver.server import parse_electrum_master_public_key
 5 | 
 6 | 
 7 | @pytest.mark.parametrize(
 8 |     "bad_master_public_key",
 9 |     [
10 |     "zpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" +
11 |     "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #bad checksum
12 |     "a tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" +
13 |     "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" +
14 |     "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" +
15 |     "T74sszGaxaf64o5s", #unparsable m number
16 |     "2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" +
17 |     "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ Vpub5fAqpSRkLmvXwqbuR61M" +
18 |     "aKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHpdvrg2LHHXkKxSXBHNWNpZz9b1Vq" +
19 |     "ADjmcCs3arSoxN3F3r", #inconsistent magic
20 |     "e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d" +
21 |     "5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442", #wrong length
22 |     "e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d" +
23 |     "5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442ZZ" #not hex
24 |     ]
25 | )
26 | 
27 | def test_parse_bad_mpk(bad_master_public_key):
28 |     try:
29 |         parse_electrum_master_public_key(bad_master_public_key, 5)
30 |         raised_error = False
31 |     except (ValueError, Exception):
32 |         raised_error = True
33 |     assert raised_error
34 | 


--------------------------------------------------------------------------------