mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
Add to Github
This commit is contained in:
3
basicswap/__init__.py
Normal file
3
basicswap/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
name = "basicswap"
|
||||
|
||||
__version__ = "0.0.1"
|
||||
2151
basicswap/basicswap.py
Normal file
2151
basicswap/basicswap.py
Normal file
File diff suppressed because it is too large
Load Diff
120
basicswap/chainparams.py
Normal file
120
basicswap/chainparams.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from enum import IntEnum
|
||||
from .util import (
|
||||
COIN,
|
||||
)
|
||||
|
||||
|
||||
class Coins(IntEnum):
|
||||
PART = 1
|
||||
BTC = 2
|
||||
LTC = 3
|
||||
# DCR = 4
|
||||
|
||||
|
||||
chainparams = {
|
||||
Coins.PART: {
|
||||
'name': 'particl',
|
||||
'ticker': 'PART',
|
||||
'message_magic': 'Bitcoin Signed Message:\n',
|
||||
'mainnet': {
|
||||
'rpcport': 51735,
|
||||
'pubkey_address': 0x38,
|
||||
'script_address': 0x3c,
|
||||
'key_prefix': 0x6c,
|
||||
'hrp': 'pw',
|
||||
'bip44': 44,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'testnet': {
|
||||
'rpcport': 51935,
|
||||
'pubkey_address': 0x76,
|
||||
'script_address': 0x7a,
|
||||
'key_prefix': 0x2e,
|
||||
'hrp': 'tpw',
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'regtest': {
|
||||
'rpcport': 51936,
|
||||
'pubkey_address': 0x76,
|
||||
'script_address': 0x7a,
|
||||
'key_prefix': 0x2e,
|
||||
'hrp': 'rtpw',
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
},
|
||||
Coins.BTC: {
|
||||
'name': 'bitcoin',
|
||||
'ticker': 'BTC',
|
||||
'message_magic': 'Bitcoin Signed Message:\n',
|
||||
'mainnet': {
|
||||
'rpcport': 8332,
|
||||
'pubkey_address': 0,
|
||||
'script_address': 5,
|
||||
'hrp': 'bc',
|
||||
'bip44': 0,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'testnet': {
|
||||
'rpcport': 18332,
|
||||
'pubkey_address': 111,
|
||||
'script_address': 196,
|
||||
'hrp': 'tb',
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'regtest': {
|
||||
'rpcport': 18443,
|
||||
'pubkey_address': 111,
|
||||
'script_address': 196,
|
||||
'hrp': 'bcrt',
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
},
|
||||
Coins.LTC: {
|
||||
'name': 'litecoin',
|
||||
'ticker': 'LTC',
|
||||
'message_magic': 'Litecoin Signed Message:\n',
|
||||
'mainnet': {
|
||||
'rpcport': 9332,
|
||||
'pubkey_address': 48,
|
||||
'script_address': 50,
|
||||
'hrp': 'ltc',
|
||||
'bip44': 2,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'testnet': {
|
||||
'rpcport': 19332,
|
||||
'pubkey_address': 111,
|
||||
'script_address': 58,
|
||||
'hrp': 'tltc',
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'regtest': {
|
||||
'rpcport': 19443,
|
||||
'pubkey_address': 111,
|
||||
'script_address': 58,
|
||||
'hrp': 'rltc',
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
}
|
||||
}
|
||||
24
basicswap/config.py
Normal file
24
basicswap/config.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import os
|
||||
|
||||
DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))
|
||||
|
||||
PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', ''))
|
||||
PARTICLD = os.getenv('PARTICLD', 'particld')
|
||||
PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli')
|
||||
PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx')
|
||||
|
||||
BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', ''))
|
||||
BITCOIND = os.getenv('BITCOIND', 'bitcoind')
|
||||
BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli')
|
||||
BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx')
|
||||
|
||||
LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', ''))
|
||||
LITECOIND = os.getenv('LITECOIND', 'litecoind')
|
||||
LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli')
|
||||
LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx')
|
||||
542
basicswap/http_server.py
Normal file
542
basicswap/http_server.py
Normal file
@@ -0,0 +1,542 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import struct
|
||||
import traceback
|
||||
import threading
|
||||
import http.client
|
||||
import urllib.parse
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from .util import (
|
||||
COIN,
|
||||
format8,
|
||||
)
|
||||
from .chainparams import (
|
||||
chainparams,
|
||||
Coins,
|
||||
)
|
||||
from .basicswap import (
|
||||
SwapTypes,
|
||||
getOfferState,
|
||||
getBidState,
|
||||
getTxState,
|
||||
getLockName,
|
||||
)
|
||||
|
||||
|
||||
def getCoinName(c):
|
||||
return chainparams[c]['name'].capitalize()
|
||||
|
||||
|
||||
def html_content_start(title, h2=None):
|
||||
content = '<!DOCTYPE html><html lang="en">\n<head>' \
|
||||
+ '<meta charset="UTF-8">' \
|
||||
+ '<title>' + title + '</title></head>' \
|
||||
+ '<body>'
|
||||
if h2 is not None:
|
||||
content += '<h2>' + h2 + '</h2>'
|
||||
return content
|
||||
|
||||
|
||||
class HttpHandler(BaseHTTPRequestHandler):
|
||||
def page_error(self, error_str):
|
||||
content = html_content_start('BasicSwap Error') \
|
||||
+ '<p>Error: ' + error_str + '</p>' \
|
||||
+ '<p><a href=\'/\'>home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def js_error(self, error_str):
|
||||
error_str_json = json.dumps({'error': error_str})
|
||||
return bytes(error_str_json, 'UTF-8')
|
||||
|
||||
def js_wallets(self, url_split):
|
||||
return bytes(json.dumps(self.server.swap_client.getWalletsInfo()), 'UTF-8')
|
||||
|
||||
def js_offers(self, url_split):
|
||||
assert(False), 'TODO'
|
||||
return bytes(json.dumps(self.server.swap_client.listOffers()), 'UTF-8')
|
||||
|
||||
def js_sentoffers(self, url_split):
|
||||
assert(False), 'TODO'
|
||||
return bytes(json.dumps(self.server.swap_client.listOffers(sent=True)), 'UTF-8')
|
||||
|
||||
def js_bids(self, url_split):
|
||||
if len(url_split) > 3:
|
||||
bid_id = bytes.fromhex(url_split[3])
|
||||
assert(len(bid_id) == 28)
|
||||
return bytes(json.dumps(self.server.swap_client.viewBid(bid_id)), 'UTF-8')
|
||||
return bytes(json.dumps(self.server.swap_client.listBids()), 'UTF-8')
|
||||
|
||||
def js_sentbids(self, url_split):
|
||||
return bytes(json.dumps(self.server.swap_client.listBids(sent=True)), 'UTF-8')
|
||||
|
||||
def js_index(self, url_split):
|
||||
return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8')
|
||||
|
||||
def page_active(self, url_split, post_string):
|
||||
swap_client = self.server.swap_client
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>Active Swaps</h3>'
|
||||
|
||||
active_swaps = swap_client.listSwapsInProgress()
|
||||
|
||||
content += '<table>'
|
||||
content += '<tr><th>Bid ID</th><th>Offer ID</th><th>Bid Status</th></tr>'
|
||||
for s in active_swaps:
|
||||
content += '<tr><td><a href=/bid/{0}>{0}</a></td><td><a href=/offer/{1}>{1}</a></td><td>{2}</td></tr>'.format(s[0].hex(), s[1], getBidState(s[2]))
|
||||
content += '</table>'
|
||||
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_wallets(self, url_split, post_string):
|
||||
swap_client = self.server.swap_client
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>Wallets</h3>'
|
||||
|
||||
if post_string != '':
|
||||
form_data = urllib.parse.parse_qs(post_string)
|
||||
form_id = form_data[b'formid'][0].decode('utf-8')
|
||||
if self.server.last_form_id.get('wallets', None) == form_id:
|
||||
content += '<p>Prevented double submit for form {}.</p>'.format(form_id)
|
||||
else:
|
||||
self.server.last_form_id['wallets'] = form_id
|
||||
|
||||
for c in Coins:
|
||||
cid = str(int(c))
|
||||
|
||||
if bytes('newaddr_' + cid, 'utf-8') in form_data:
|
||||
swap_client.cacheNewAddressForCoin(c)
|
||||
|
||||
if bytes('withdraw_' + cid, 'utf-8') in form_data:
|
||||
value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8')
|
||||
address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8')
|
||||
txid = swap_client.withdrawCoin(c, value, address)
|
||||
ticker = swap_client.getTicker(c)
|
||||
content += '<p>Withdrew {} {} to address {}<br/>In txid: {}</p>'.format(value, ticker, address, txid)
|
||||
|
||||
wallets = swap_client.getWalletsInfo()
|
||||
|
||||
content += '<form method="post">'
|
||||
for k, w in wallets.items():
|
||||
cid = str(int(k))
|
||||
content += '<h4>' + w['name'] + '</h4>' \
|
||||
+ '<table>' \
|
||||
+ '<tr><td>Balance:</td><td>' + str(w['balance']) + '</td></tr>' \
|
||||
+ '<tr><td>Blocks:</td><td>' + str(w['blocks']) + '</td></tr>' \
|
||||
+ '<tr><td>Synced:</td><td>' + str(w['synced']) + '</td></tr>' \
|
||||
+ '<tr><td><input type="submit" name="newaddr_' + cid + '" value="Deposit Address"></td><td>' + str(w['deposit_address']) + '</td></tr>' \
|
||||
+ '<tr><td><input type="submit" name="withdraw_' + cid + '" value="Withdraw"></td><td>Amount: <input type="text" name="amt_' + cid + '"></td><td>Address: <input type="text" name="to_' + cid + '"></td></tr>' \
|
||||
+ '</table>'
|
||||
|
||||
content += '<input type="hidden" name="formid" value="' + os.urandom(8).hex() + '"></form>'
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def make_coin_select(self, name, coins):
|
||||
s = '<select name="' + name + '"><option value="-1">-- Select Coin --</option>'
|
||||
for c in coins:
|
||||
s += '<option value="{}">{}</option>'.format(*c)
|
||||
s += '</select>'
|
||||
return s
|
||||
|
||||
def page_newoffer(self, url_split, post_string):
|
||||
swap_client = self.server.swap_client
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>New Offer</h3>'
|
||||
|
||||
if post_string != '':
|
||||
form_data = urllib.parse.parse_qs(post_string)
|
||||
form_id = form_data[b'formid'][0].decode('utf-8')
|
||||
if self.server.last_form_id.get('newoffer', None) == form_id:
|
||||
content += '<p>Prevented double submit for form {}.</p>'.format(form_id)
|
||||
else:
|
||||
self.server.last_form_id['newoffer'] = form_id
|
||||
|
||||
try:
|
||||
coin_from = Coins(int(form_data[b'coin_from'][0]))
|
||||
except Exception:
|
||||
raise ValueError('Unknown Coin From')
|
||||
try:
|
||||
coin_to = Coins(int(form_data[b'coin_to'][0]))
|
||||
except Exception:
|
||||
raise ValueError('Unknown Coin From')
|
||||
|
||||
value_from = int(float(form_data[b'amt_from'][0]) * COIN)
|
||||
value_to = int(float(form_data[b'amt_to'][0]) * COIN)
|
||||
min_bid = int(value_from)
|
||||
rate = int((value_to / value_from) * COIN)
|
||||
autoaccept = True if b'autoaccept' in form_data else False
|
||||
# TODO: More accurate rate
|
||||
# assert(value_to == (value_from * rate) // COIN)
|
||||
offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, SwapTypes.SELLER_FIRST, auto_accept_bids=autoaccept)
|
||||
content += '<p><a href="/offer/' + offer_id.hex() + '">Sent Offer ' + offer_id.hex() + '</a><br/>Rate: ' + format8(rate) + '</p>'
|
||||
|
||||
coins = []
|
||||
|
||||
for k, v in swap_client.coin_clients.items():
|
||||
if v['connection_type'] == 'rpc':
|
||||
coins.append((int(k), getCoinName(k)))
|
||||
|
||||
content += '<form method="post">'
|
||||
|
||||
content += '<table>'
|
||||
content += '<tr><td>Coin From</td><td>' + self.make_coin_select('coin_from', coins) + '</td><td>Amount From</td><td><input type="text" name="amt_from"></td></tr>'
|
||||
content += '<tr><td>Coin To</td><td>' + self.make_coin_select('coin_to', coins) + '</td><td>Amount To</td><td><input type="text" name="amt_to"></td></tr>'
|
||||
content += '<tr><td>Auto Accept Bids</td><td><input type="checkbox" name="autoaccept" value="aa" checked></td></tr>'
|
||||
content += '</table>'
|
||||
|
||||
content += '<input type="submit" value="Submit">'
|
||||
content += '<input type="hidden" name="formid" value="' + os.urandom(8).hex() + '"></form>'
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_offer(self, url_split, post_string):
|
||||
assert(len(url_split) > 2), 'Offer ID not specified'
|
||||
try:
|
||||
offer_id = bytes.fromhex(url_split[2])
|
||||
assert(len(offer_id) == 28)
|
||||
except Exception:
|
||||
raise ValueError('Bad offer ID')
|
||||
swap_client = self.server.swap_client
|
||||
offer = swap_client.getOffer(offer_id)
|
||||
assert(offer), 'Unknown offer ID'
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>Offer: ' + offer_id.hex() + '</h3>'
|
||||
|
||||
if post_string != '':
|
||||
form_data = urllib.parse.parse_qs(post_string)
|
||||
form_id = form_data[b'formid'][0].decode('utf-8')
|
||||
if self.server.last_form_id.get('offer', None) == form_id:
|
||||
content += '<p>Prevented double submit for form {}.</p>'.format(form_id)
|
||||
else:
|
||||
self.server.last_form_id['offer'] = form_id
|
||||
bid_id = swap_client.postBid(offer_id, offer.amount_from)
|
||||
content += '<p><a href="/bid/' + bid_id.hex() + '">Sent Bid ' + bid_id.hex() + '</a></p>'
|
||||
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ticker_from = swap_client.getTicker(coin_from)
|
||||
ticker_to = swap_client.getTicker(coin_to)
|
||||
|
||||
tr = '<tr><td>{}</td><td>{}</td></tr>'
|
||||
content += '<table>'
|
||||
content += tr.format('Offer State', getOfferState(offer.state))
|
||||
content += tr.format('Coin From', getCoinName(coin_from))
|
||||
content += tr.format('Coin To', getCoinName(coin_to))
|
||||
content += tr.format('Amount From', format8(offer.amount_from) + ' ' + ticker_from)
|
||||
content += tr.format('Amount To', format8((offer.amount_from * offer.rate) // COIN) + ' ' + ticker_to)
|
||||
content += tr.format('Rate', format8(offer.rate) + ' ' + ticker_from + '/' + ticker_to)
|
||||
content += tr.format('Script Lock Type', getLockName(offer.lock_type))
|
||||
content += tr.format('Script Lock Value', offer.lock_value)
|
||||
content += tr.format('Address From', offer.addr_from)
|
||||
content += tr.format('Created At', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(offer.created_at)))
|
||||
content += tr.format('Expired At', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(offer.expire_at)))
|
||||
content += tr.format('Sent', 'True' if offer.was_sent else 'False')
|
||||
|
||||
if offer.was_sent:
|
||||
content += tr.format('Auto Accept Bids', 'True' if offer.auto_accept_bids else 'False')
|
||||
content += '</table>'
|
||||
|
||||
bids = swap_client.listBids(offer_id=offer_id)
|
||||
|
||||
content += '<h4>Bids</h4><table>'
|
||||
content += '<tr><th>Bid ID</th><th>Bid Amount</th><th>Bid Status</th><th>ITX Status</th><th>PTX Status</th></tr>'
|
||||
for b in bids:
|
||||
content += '<tr><td><a href=/bid/{0}>{0}</a></td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td></tr>'.format(b.bid_id.hex(), format8(b.amount), getBidState(b.state), getTxState(b.initiate_txn_state), getTxState(b.participate_txn_state))
|
||||
content += '</table>'
|
||||
|
||||
content += '<form method="post">'
|
||||
content += '<input type="submit" value="Send Bid">'
|
||||
content += '<input type="hidden" name="formid" value="' + os.urandom(8).hex() + '"></form>'
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_offers(self, url_split, sent=False):
|
||||
swap_client = self.server.swap_client
|
||||
offers = swap_client.listOffers(sent)
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>' + ('Sent ' if sent else '') + 'Offers</h3>'
|
||||
|
||||
content += '<table>'
|
||||
content += '<tr><th>Offer ID</th><th>Coin From</th><th>Coin To</th><th>Amount From</th><th>Amount To</th><th>Rate</th></tr>'
|
||||
for o in offers:
|
||||
coin_from_name = getCoinName(Coins(o.coin_from))
|
||||
coin_to_name = getCoinName(Coins(o.coin_to))
|
||||
amount_to = (o.amount_from * o.rate) // COIN
|
||||
content += '<tr><td><a href=/offer/{0}>{0}</a></td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td>{5}</td></tr>'.format(o.offer_id.hex(), coin_from_name, coin_to_name, format8(o.amount_from), format8(amount_to), format8(o.rate))
|
||||
|
||||
content += '</table>'
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_bid(self, url_split, post_string):
|
||||
assert(len(url_split) > 2), 'Bid ID not specified'
|
||||
try:
|
||||
bid_id = bytes.fromhex(url_split[2])
|
||||
assert(len(bid_id) == 28)
|
||||
except Exception:
|
||||
raise ValueError('Bad bid ID')
|
||||
swap_client = self.server.swap_client
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>Bid: ' + bid_id.hex() + '</h3>'
|
||||
|
||||
show_txns = False
|
||||
if post_string != '':
|
||||
form_data = urllib.parse.parse_qs(post_string)
|
||||
form_id = form_data[b'formid'][0].decode('utf-8')
|
||||
if self.server.last_form_id.get('bid', None) == form_id:
|
||||
content += '<p>Prevented double submit for form {}.</p>'.format(form_id)
|
||||
else:
|
||||
self.server.last_form_id['bid'] = form_id
|
||||
if b'abandon_bid' in form_data:
|
||||
try:
|
||||
swap_client.abandonBid(bid_id)
|
||||
content += '<p>Bid abandoned</p>'
|
||||
except Exception as e:
|
||||
content += '<p>Error' + str(e) + '</p>'
|
||||
if b'accept_bid' in form_data:
|
||||
try:
|
||||
swap_client.acceptBid(bid_id)
|
||||
content += '<p>Bid accepted</p>'
|
||||
except Exception as e:
|
||||
content += '<p>Error' + str(e) + '</p>'
|
||||
if b'show_txns' in form_data:
|
||||
show_txns = True
|
||||
|
||||
bid, offer = swap_client.getBidAndOffer(bid_id)
|
||||
assert(bid), 'Unknown bid ID'
|
||||
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
ticker_from = swap_client.getTicker(coin_from)
|
||||
ticker_to = swap_client.getTicker(coin_to)
|
||||
|
||||
tr = '<tr><td>{}</td><td>{}</td></tr>'
|
||||
content += '<table>'
|
||||
|
||||
content += tr.format('Swap', format8(bid.amount) + ' ' + ticker_from + ' for ' + format8((bid.amount * offer.rate) // COIN) + ' ' + ticker_to)
|
||||
content += tr.format('Bid State', getBidState(bid.state))
|
||||
content += tr.format('ITX State', getTxState(bid.initiate_txn_state))
|
||||
content += tr.format('PTX State', getTxState(bid.participate_txn_state))
|
||||
content += tr.format('Offer', '<a href="/offer/' + bid.offer_id.hex() + '">' + bid.offer_id.hex() + '</a>')
|
||||
content += tr.format('Address From', bid.bid_addr)
|
||||
content += tr.format('Proof of Funds', bid.proof_address)
|
||||
content += tr.format('Created At', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(bid.created_at)))
|
||||
content += tr.format('Expired At', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(bid.expire_at)))
|
||||
content += tr.format('Sent', 'True' if bid.was_sent else 'False')
|
||||
content += tr.format('Received', 'True' if bid.was_received else 'False')
|
||||
content += tr.format('Initiate Tx', 'None' if not bid.initiate_txid else bid.initiate_txid.hex())
|
||||
content += tr.format('Initiate Conf', 'None' if not bid.initiate_txn_conf else bid.initiate_txn_conf)
|
||||
content += tr.format('Participate Tx', 'None' if not bid.participate_txid else bid.participate_txid.hex())
|
||||
content += tr.format('Participate Conf', 'None' if not bid.participate_txn_conf else bid.participate_txn_conf)
|
||||
if show_txns:
|
||||
content += tr.format('Initiate Tx Refund', 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex())
|
||||
content += tr.format('Participate Tx Refund', 'None' if not bid.participate_txn_refund else bid.participate_txn_refund.hex())
|
||||
content += tr.format('Initiate Spend Tx', 'None' if not bid.initiate_spend_txid else (bid.initiate_spend_txid.hex() + ' {}'.format(bid.initiate_spend_n)))
|
||||
content += tr.format('Participate Spend Tx', 'None' if not bid.participate_spend_txid else (bid.participate_spend_txid.hex() + ' {}'.format(bid.participate_spend_n)))
|
||||
content += '</table>'
|
||||
|
||||
content += '<form method="post">'
|
||||
if bid.was_received:
|
||||
content += '<input name="accept_bid" type="submit" value="Accept Bid"><br/>'
|
||||
content += '<input name="abandon_bid" type="submit" value="Abandon Bid">'
|
||||
content += '<input name="show_txns" type="submit" value="Show More Info">'
|
||||
content += '<input type="hidden" name="formid" value="' + os.urandom(8).hex() + '"></form>'
|
||||
|
||||
content += '<h4>Old States</h4><table><tr><th>State</th><th>Set At</th></tr>'
|
||||
num_states = len(bid.states) // 12
|
||||
for i in range(num_states):
|
||||
up = struct.unpack_from('<iq', bid.states[i * 12:(i + 1) * 12])
|
||||
content += '<tr><td>Bid {}</td><td>{}</td></tr>'.format(getBidState(up[0]), time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(up[1])))
|
||||
if bid.initiate_txn_states is not None:
|
||||
num_states = len(bid.initiate_txn_states) // 12
|
||||
for i in range(num_states):
|
||||
up = struct.unpack_from('<iq', bid.initiate_txn_states[i * 12:(i + 1) * 12])
|
||||
content += '<tr><td>ITX {}</td><td>{}</td></tr>'.format(getTxState(up[0]), time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(up[1])))
|
||||
if bid.participate_txn_states is not None:
|
||||
num_states = len(bid.participate_txn_states) // 12
|
||||
for i in range(num_states):
|
||||
up = struct.unpack_from('<iq', bid.participate_txn_states[i * 12:(i + 1) * 12])
|
||||
content += '<tr><td>PTX {}</td><td>{}</td></tr>'.format(getTxState(up[0]), time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(up[1])))
|
||||
content += '</table>'
|
||||
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_bids(self, url_split, post_string, sent=False):
|
||||
swap_client = self.server.swap_client
|
||||
bids = swap_client.listBids(sent=sent)
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>' + ('Sent ' if sent else '') + 'Bids</h3>'
|
||||
|
||||
content += '<table>'
|
||||
content += '<tr><th>Bid ID</th><th>Offer ID</th><th>Bid Status</th><th>ITX Status</th><th>PTX Status</th></tr>'
|
||||
for b in bids:
|
||||
content += '<tr><td><a href=/bid/{0}>{0}</a></td><td><a href=/offer/{1}>{1}</a></td><td>{2}</td><td>{3}</td><td>{4}</td></tr>'.format(b.bid_id.hex(), b.offer_id.hex(), getBidState(b.state), getTxState(b.initiate_txn_state), getTxState(b.participate_txn_state))
|
||||
content += '</table>'
|
||||
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_watched(self, url_split, post_string):
|
||||
swap_client = self.server.swap_client
|
||||
watched_outputs, last_scanned = swap_client.listWatchedOutputs()
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<h3>Watched Outputs</h3>'
|
||||
|
||||
for c in last_scanned:
|
||||
content += '<p>' + getCoinName(c[0]) + ' Scanned Height: ' + str(c[1]) + '</p>'
|
||||
|
||||
content += '<table>'
|
||||
content += '<tr><th>Bid ID</th><th>Chain</th><th>Txid</th><th>Index</th><th>Type</th></tr>'
|
||||
for o in watched_outputs:
|
||||
content += '<tr><td><a href=/bid/{0}>{0}</a></td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td></tr>'.format(o[1].hex(), getCoinName(o[0]), o[2], o[3], int(o[4]))
|
||||
content += '</table>'
|
||||
|
||||
content += '<p><a href="/">home</a></p></body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def page_index(self, url_split):
|
||||
swap_client = self.server.swap_client
|
||||
summary = swap_client.getSummary()
|
||||
|
||||
content = html_content_start(self.server.title, self.server.title) \
|
||||
+ '<p><a href="/wallets">View Wallets</a></p>' \
|
||||
+ '<p>' \
|
||||
+ 'Network: ' + str(summary['network']) + '<br/>' \
|
||||
+ '<a href="/active">Swaps in progress: ' + str(summary['num_swapping']) + '</a><br/>' \
|
||||
+ '<a href="/offers">Network Offers: ' + str(summary['num_network_offers']) + '</a><br/>' \
|
||||
+ '<a href="/sentoffers">Sent Offers: ' + str(summary['num_sent_offers']) + '</a><br/>' \
|
||||
+ '<a href="/bids">Received Bids: ' + str(summary['num_recv_bids']) + '</a><br/>' \
|
||||
+ '<a href="/sentbids">Sent Bids: ' + str(summary['num_sent_bids']) + '</a><br/>' \
|
||||
+ '<a href="/watched">Watched Outputs: ' + str(summary['num_watched_outputs']) + '</a><br/>' \
|
||||
+ '</p>' \
|
||||
+ '<p>' \
|
||||
+ '<a href="/newoffer">New Offer</a><br/>' \
|
||||
+ '</p>'
|
||||
content += '</body></html>'
|
||||
return bytes(content, 'UTF-8')
|
||||
|
||||
def putHeaders(self, status_code, content_type):
|
||||
self.send_response(status_code)
|
||||
if self.server.allow_cors:
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Content-type', content_type)
|
||||
self.end_headers()
|
||||
|
||||
def handle_http(self, status_code, path, post_string=''):
|
||||
url_split = self.path.split('/')
|
||||
if len(url_split) > 1 and url_split[1] == 'json':
|
||||
try:
|
||||
self.putHeaders(status_code, 'text/plain')
|
||||
func = self.js_index
|
||||
if len(url_split) > 2:
|
||||
func = {'wallets': self.js_wallets,
|
||||
'offers': self.js_offers,
|
||||
'sentoffers': self.js_sentoffers,
|
||||
'bids': self.js_bids,
|
||||
'sentbids': self.js_sentbids,
|
||||
}.get(url_split[2], self.js_index)
|
||||
return func(url_split)
|
||||
except Exception as e:
|
||||
return self.js_error(str(e))
|
||||
try:
|
||||
self.putHeaders(status_code, 'text/html')
|
||||
if len(url_split) > 1:
|
||||
if url_split[1] == 'active':
|
||||
return self.page_active(url_split, post_string)
|
||||
if url_split[1] == 'wallets':
|
||||
return self.page_wallets(url_split, post_string)
|
||||
if url_split[1] == 'offer':
|
||||
return self.page_offer(url_split, post_string)
|
||||
if url_split[1] == 'offers':
|
||||
return self.page_offers(url_split)
|
||||
if url_split[1] == 'newoffer':
|
||||
return self.page_newoffer(url_split, post_string)
|
||||
if url_split[1] == 'sentoffers':
|
||||
return self.page_offers(url_split, sent=True)
|
||||
if url_split[1] == 'bid':
|
||||
return self.page_bid(url_split, post_string)
|
||||
if url_split[1] == 'bids':
|
||||
return self.page_bids(url_split, post_string)
|
||||
if url_split[1] == 'sentbids':
|
||||
return self.page_bids(url_split, post_string, sent=True)
|
||||
if url_split[1] == 'watched':
|
||||
return self.page_watched(url_split, post_string)
|
||||
return self.page_index(url_split)
|
||||
except Exception as e:
|
||||
traceback.print_exc() # TODO: Remove
|
||||
return self.page_error(str(e))
|
||||
|
||||
def do_GET(self):
|
||||
response = self.handle_http(200, self.path)
|
||||
self.wfile.write(response)
|
||||
|
||||
def do_POST(self):
|
||||
post_string = self.rfile.read(int(self.headers['Content-Length']))
|
||||
response = self.handle_http(200, self.path, post_string)
|
||||
self.wfile.write(response)
|
||||
|
||||
def do_HEAD(self):
|
||||
self.putHeaders(200, 'text/html')
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200, 'ok')
|
||||
if self.server.allow_cors:
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Headers', '*')
|
||||
self.end_headers()
|
||||
|
||||
|
||||
class HttpThread(threading.Thread, HTTPServer):
|
||||
def __init__(self, fp, host_name, port_no, allow_cors, swap_client):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.stop_event = threading.Event()
|
||||
self.fp = fp
|
||||
self.host_name = host_name
|
||||
self.port_no = port_no
|
||||
self.allow_cors = allow_cors
|
||||
self.swap_client = swap_client
|
||||
self.title = 'Simple Atomic Swap Demo'
|
||||
self.last_form_id = dict()
|
||||
|
||||
self.timeout = 60
|
||||
HTTPServer.__init__(self, (self.host_name, self.port_no), HttpHandler)
|
||||
|
||||
def stop(self):
|
||||
self.stop_event.set()
|
||||
|
||||
# Send fake request
|
||||
conn = http.client.HTTPConnection(self.host_name, self.port_no)
|
||||
conn.connect()
|
||||
conn.request('GET', '/none')
|
||||
response = conn.getresponse()
|
||||
data = response.read()
|
||||
conn.close()
|
||||
|
||||
def stopped(self):
|
||||
return self.stop_event.is_set()
|
||||
|
||||
def serve_forever(self):
|
||||
while not self.stopped():
|
||||
self.handle_request()
|
||||
self.socket.close()
|
||||
|
||||
def run(self):
|
||||
self.serve_forever()
|
||||
386
basicswap/key.py
Normal file
386
basicswap/key.py
Normal file
@@ -0,0 +1,386 @@
|
||||
# Copyright (c) 2019 Pieter Wuille
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test-only secp256k1 elliptic curve implementation
|
||||
|
||||
WARNING: This code is slow, uses bad randomness, does not properly protect
|
||||
keys, and is trivially vulnerable to side channel attacks. Do not use for
|
||||
anything but tests."""
|
||||
import random
|
||||
|
||||
def modinv(a, n):
|
||||
"""Compute the modular inverse of a modulo n
|
||||
|
||||
See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
|
||||
"""
|
||||
t1, t2 = 0, 1
|
||||
r1, r2 = n, a
|
||||
while r2 != 0:
|
||||
q = r1 // r2
|
||||
t1, t2 = t2, t1 - q * t2
|
||||
r1, r2 = r2, r1 - q * r2
|
||||
if r1 > 1:
|
||||
return None
|
||||
if t1 < 0:
|
||||
t1 += n
|
||||
return t1
|
||||
|
||||
def jacobi_symbol(n, k):
|
||||
"""Compute the Jacobi symbol of n modulo k
|
||||
|
||||
See http://en.wikipedia.org/wiki/Jacobi_symbol
|
||||
|
||||
For our application k is always prime, so this is the same as the Legendre symbol."""
|
||||
assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
|
||||
n %= k
|
||||
t = 0
|
||||
while n != 0:
|
||||
while n & 1 == 0:
|
||||
n >>= 1
|
||||
r = k & 7
|
||||
t ^= (r == 3 or r == 5)
|
||||
n, k = k, n
|
||||
t ^= (n & k & 3 == 3)
|
||||
n = n % k
|
||||
if k == 1:
|
||||
return -1 if t else 1
|
||||
return 0
|
||||
|
||||
def modsqrt(a, p):
|
||||
"""Compute the square root of a modulo p when p % 4 = 3.
|
||||
|
||||
The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm
|
||||
|
||||
Limiting this function to only work for p % 4 = 3 means we don't need to
|
||||
iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd
|
||||
is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4)
|
||||
|
||||
secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4.
|
||||
"""
|
||||
if p % 4 != 3:
|
||||
raise NotImplementedError("modsqrt only implemented for p % 4 = 3")
|
||||
sqrt = pow(a, (p + 1)//4, p)
|
||||
if pow(sqrt, 2, p) == a % p:
|
||||
return sqrt
|
||||
return None
|
||||
|
||||
class EllipticCurve:
|
||||
def __init__(self, p, a, b):
|
||||
"""Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
|
||||
self.p = p
|
||||
self.a = a % p
|
||||
self.b = b % p
|
||||
|
||||
def affine(self, p1):
|
||||
"""Convert a Jacobian point tuple p1 to affine form, or None if at infinity.
|
||||
|
||||
An affine point is represented as the Jacobian (x, y, 1)"""
|
||||
x1, y1, z1 = p1
|
||||
if z1 == 0:
|
||||
return None
|
||||
inv = modinv(z1, self.p)
|
||||
inv_2 = (inv**2) % self.p
|
||||
inv_3 = (inv_2 * inv) % self.p
|
||||
return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
|
||||
|
||||
def negate(self, p1):
|
||||
"""Negate a Jacobian point tuple p1."""
|
||||
x1, y1, z1 = p1
|
||||
return (x1, (self.p - y1) % self.p, z1)
|
||||
|
||||
def on_curve(self, p1):
|
||||
"""Determine whether a Jacobian tuple p is on the curve (and not infinity)"""
|
||||
x1, y1, z1 = p1
|
||||
z2 = pow(z1, 2, self.p)
|
||||
z4 = pow(z2, 2, self.p)
|
||||
return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0
|
||||
|
||||
def is_x_coord(self, x):
|
||||
"""Test whether x is a valid X coordinate on the curve."""
|
||||
x_3 = pow(x, 3, self.p)
|
||||
return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
|
||||
|
||||
def lift_x(self, x):
|
||||
"""Given an X coordinate on the curve, return a corresponding affine point."""
|
||||
x_3 = pow(x, 3, self.p)
|
||||
v = x_3 + self.a * x + self.b
|
||||
y = modsqrt(v, self.p)
|
||||
if y is None:
|
||||
return None
|
||||
return (x, y, 1)
|
||||
|
||||
def double(self, p1):
|
||||
"""Double a Jacobian tuple p1
|
||||
|
||||
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling"""
|
||||
x1, y1, z1 = p1
|
||||
if z1 == 0:
|
||||
return (0, 1, 0)
|
||||
y1_2 = (y1**2) % self.p
|
||||
y1_4 = (y1_2**2) % self.p
|
||||
x1_2 = (x1**2) % self.p
|
||||
s = (4*x1*y1_2) % self.p
|
||||
m = 3*x1_2
|
||||
if self.a:
|
||||
m += self.a * pow(z1, 4, self.p)
|
||||
m = m % self.p
|
||||
x2 = (m**2 - 2*s) % self.p
|
||||
y2 = (m*(s - x2) - 8*y1_4) % self.p
|
||||
z2 = (2*y1*z1) % self.p
|
||||
return (x2, y2, z2)
|
||||
|
||||
def add_mixed(self, p1, p2):
|
||||
"""Add a Jacobian tuple p1 and an affine tuple p2
|
||||
|
||||
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
assert(z2 == 1)
|
||||
# Adding to the point at infinity is a no-op
|
||||
if z1 == 0:
|
||||
return p2
|
||||
z1_2 = (z1**2) % self.p
|
||||
z1_3 = (z1_2 * z1) % self.p
|
||||
u2 = (x2 * z1_2) % self.p
|
||||
s2 = (y2 * z1_3) % self.p
|
||||
if x1 == u2:
|
||||
if (y1 != s2):
|
||||
# p1 and p2 are inverses. Return the point at infinity.
|
||||
return (0, 1, 0)
|
||||
# p1 == p2. The formulas below fail when the two points are equal.
|
||||
return self.double(p1)
|
||||
h = u2 - x1
|
||||
r = s2 - y1
|
||||
h_2 = (h**2) % self.p
|
||||
h_3 = (h_2 * h) % self.p
|
||||
u1_h_2 = (x1 * h_2) % self.p
|
||||
x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
|
||||
y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p
|
||||
z3 = (h*z1) % self.p
|
||||
return (x3, y3, z3)
|
||||
|
||||
def add(self, p1, p2):
|
||||
"""Add two Jacobian tuples p1 and p2
|
||||
|
||||
See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition"""
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
# Adding the point at infinity is a no-op
|
||||
if z1 == 0:
|
||||
return p2
|
||||
if z2 == 0:
|
||||
return p1
|
||||
# Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1
|
||||
if z1 == 1:
|
||||
return self.add_mixed(p2, p1)
|
||||
if z2 == 1:
|
||||
return self.add_mixed(p1, p2)
|
||||
z1_2 = (z1**2) % self.p
|
||||
z1_3 = (z1_2 * z1) % self.p
|
||||
z2_2 = (z2**2) % self.p
|
||||
z2_3 = (z2_2 * z2) % self.p
|
||||
u1 = (x1 * z2_2) % self.p
|
||||
u2 = (x2 * z1_2) % self.p
|
||||
s1 = (y1 * z2_3) % self.p
|
||||
s2 = (y2 * z1_3) % self.p
|
||||
if u1 == u2:
|
||||
if (s1 != s2):
|
||||
# p1 and p2 are inverses. Return the point at infinity.
|
||||
return (0, 1, 0)
|
||||
# p1 == p2. The formulas below fail when the two points are equal.
|
||||
return self.double(p1)
|
||||
h = u2 - u1
|
||||
r = s2 - s1
|
||||
h_2 = (h**2) % self.p
|
||||
h_3 = (h_2 * h) % self.p
|
||||
u1_h_2 = (u1 * h_2) % self.p
|
||||
x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
|
||||
y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p
|
||||
z3 = (h*z1*z2) % self.p
|
||||
return (x3, y3, z3)
|
||||
|
||||
def mul(self, ps):
|
||||
"""Compute a (multi) point multiplication
|
||||
|
||||
ps is a list of (Jacobian tuple, scalar) pairs.
|
||||
"""
|
||||
r = (0, 1, 0)
|
||||
for i in range(255, -1, -1):
|
||||
r = self.double(r)
|
||||
for (p, n) in ps:
|
||||
if ((n >> i) & 1):
|
||||
r = self.add(r, p)
|
||||
return r
|
||||
|
||||
SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7)
|
||||
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
|
||||
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
|
||||
|
||||
class ECPubKey():
|
||||
"""A secp256k1 public key"""
|
||||
|
||||
def __init__(self):
|
||||
"""Construct an uninitialized public key"""
|
||||
self.valid = False
|
||||
|
||||
def set(self, data):
|
||||
"""Construct a public key from a serialization in compressed or uncompressed format"""
|
||||
if (len(data) == 65 and data[0] == 0x04):
|
||||
p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1)
|
||||
self.valid = SECP256K1.on_curve(p)
|
||||
if self.valid:
|
||||
self.p = p
|
||||
self.compressed = False
|
||||
elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)):
|
||||
x = int.from_bytes(data[1:33], 'big')
|
||||
if SECP256K1.is_x_coord(x):
|
||||
p = SECP256K1.lift_x(x)
|
||||
# if the oddness of the y co-ord isn't correct, find the other
|
||||
# valid y
|
||||
if (p[1] & 1) != (data[0] & 1):
|
||||
p = SECP256K1.negate(p)
|
||||
self.p = p
|
||||
self.valid = True
|
||||
self.compressed = True
|
||||
else:
|
||||
self.valid = False
|
||||
else:
|
||||
self.valid = False
|
||||
|
||||
@property
|
||||
def is_compressed(self):
|
||||
return self.compressed
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return self.valid
|
||||
|
||||
def get_bytes(self):
|
||||
assert(self.valid)
|
||||
p = SECP256K1.affine(self.p)
|
||||
if p is None:
|
||||
return None
|
||||
if self.compressed:
|
||||
return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big')
|
||||
else:
|
||||
return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big')
|
||||
|
||||
def verify_ecdsa(self, sig, msg, low_s=True):
|
||||
"""Verify a strictly DER-encoded ECDSA signature against this pubkey.
|
||||
|
||||
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
|
||||
ECDSA verifier algorithm"""
|
||||
assert(self.valid)
|
||||
|
||||
# Extract r and s from the DER formatted signature. Return false for
|
||||
# any DER encoding errors.
|
||||
if (sig[1] + 2 != len(sig)):
|
||||
return False
|
||||
if (len(sig) < 4):
|
||||
return False
|
||||
if (sig[0] != 0x30):
|
||||
return False
|
||||
if (sig[2] != 0x02):
|
||||
return False
|
||||
rlen = sig[3]
|
||||
if (len(sig) < 6 + rlen):
|
||||
return False
|
||||
if rlen < 1 or rlen > 33:
|
||||
return False
|
||||
if sig[4] >= 0x80:
|
||||
return False
|
||||
if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)):
|
||||
return False
|
||||
r = int.from_bytes(sig[4:4+rlen], 'big')
|
||||
if (sig[4+rlen] != 0x02):
|
||||
return False
|
||||
slen = sig[5+rlen]
|
||||
if slen < 1 or slen > 33:
|
||||
return False
|
||||
if (len(sig) != 6 + rlen + slen):
|
||||
return False
|
||||
if sig[6+rlen] >= 0x80:
|
||||
return False
|
||||
if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)):
|
||||
return False
|
||||
s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big')
|
||||
|
||||
# Verify that r and s are within the group order
|
||||
if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER:
|
||||
return False
|
||||
if low_s and s >= SECP256K1_ORDER_HALF:
|
||||
return False
|
||||
z = int.from_bytes(msg, 'big')
|
||||
|
||||
# Run verifier algorithm on r, s
|
||||
w = modinv(s, SECP256K1_ORDER)
|
||||
u1 = z*w % SECP256K1_ORDER
|
||||
u2 = r*w % SECP256K1_ORDER
|
||||
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)]))
|
||||
if R is None or R[0] != r:
|
||||
return False
|
||||
return True
|
||||
|
||||
class ECKey():
|
||||
"""A secp256k1 private key"""
|
||||
|
||||
def __init__(self):
|
||||
self.valid = False
|
||||
|
||||
def set(self, secret, compressed):
|
||||
"""Construct a private key object with given 32-byte secret and compressed flag."""
|
||||
assert(len(secret) == 32)
|
||||
secret = int.from_bytes(secret, 'big')
|
||||
self.valid = (secret > 0 and secret < SECP256K1_ORDER)
|
||||
if self.valid:
|
||||
self.secret = secret
|
||||
self.compressed = compressed
|
||||
|
||||
def generate(self, compressed=True):
|
||||
"""Generate a random private key (compressed or uncompressed)."""
|
||||
self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
|
||||
|
||||
def get_bytes(self):
|
||||
"""Retrieve the 32-byte representation of this key."""
|
||||
assert(self.valid)
|
||||
return self.secret.to_bytes(32, 'big')
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return self.valid
|
||||
|
||||
@property
|
||||
def is_compressed(self):
|
||||
return self.compressed
|
||||
|
||||
def get_pubkey(self):
|
||||
"""Compute an ECPubKey object for this secret key."""
|
||||
assert(self.valid)
|
||||
ret = ECPubKey()
|
||||
p = SECP256K1.mul([(SECP256K1_G, self.secret)])
|
||||
ret.p = p
|
||||
ret.valid = True
|
||||
ret.compressed = self.compressed
|
||||
return ret
|
||||
|
||||
def sign_ecdsa(self, msg, low_s=True):
|
||||
"""Construct a DER-encoded ECDSA signature with this key.
|
||||
|
||||
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
|
||||
ECDSA signer algorithm."""
|
||||
assert(self.valid)
|
||||
z = int.from_bytes(msg, 'big')
|
||||
# Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation)
|
||||
k = random.randrange(1, SECP256K1_ORDER)
|
||||
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
|
||||
r = R[0] % SECP256K1_ORDER
|
||||
s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER
|
||||
if low_s and s > SECP256K1_ORDER_HALF:
|
||||
s = SECP256K1_ORDER - s
|
||||
# Represent in DER format. The byte representations of r and s have
|
||||
# length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33
|
||||
# bytes).
|
||||
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
|
||||
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
|
||||
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
|
||||
46
basicswap/messages.proto
Normal file
46
basicswap/messages.proto
Normal file
@@ -0,0 +1,46 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package basicswap;
|
||||
|
||||
/* Step 1, seller -> network */
|
||||
message OfferMessage {
|
||||
uint32 coin_from = 1;
|
||||
uint32 coin_to = 2;
|
||||
uint64 amount_from = 3;
|
||||
uint64 rate = 4;
|
||||
uint64 min_bid_amount = 5;
|
||||
uint64 time_valid = 6;
|
||||
enum LockType {
|
||||
NOT_SET = 0;
|
||||
SEQUENCE_LOCK_BLOCKS = 1;
|
||||
SEQUENCE_LOCK_TIME = 2;
|
||||
}
|
||||
LockType lock_type = 7;
|
||||
uint32 lock_value = 8;
|
||||
uint32 swap_type = 9;
|
||||
|
||||
/* optional */
|
||||
string proof_address = 10;
|
||||
string proof_signature = 11;
|
||||
bytes pkhash_seller = 12;
|
||||
bytes secret_hash = 13;
|
||||
}
|
||||
|
||||
/* Step 2, buyer -> seller */
|
||||
message BidMessage {
|
||||
bytes offer_msg_id = 1;
|
||||
uint64 time_valid = 2; /* seconds bid is valid for */
|
||||
uint64 amount = 3; /* amount of amount_from bid is for */
|
||||
|
||||
/* optional */
|
||||
bytes pkhash_buyer = 4; /* buyer's address to receive amount_from */
|
||||
string proof_address = 5;
|
||||
string proof_signature = 6;
|
||||
}
|
||||
|
||||
/* Step 3, seller -> buyer */
|
||||
message BidAcceptMessage {
|
||||
bytes bid_msg_id = 1;
|
||||
bytes initiate_txid = 2;
|
||||
bytes contract_script = 3;
|
||||
}
|
||||
310
basicswap/messages_pb2.py
Normal file
310
basicswap/messages_pb2.py
Normal file
@@ -0,0 +1,310 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: messages.proto
|
||||
|
||||
import sys
|
||||
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='messages.proto',
|
||||
package='basicswap',
|
||||
syntax='proto3',
|
||||
serialized_options=None,
|
||||
serialized_pb=_b('\n\x0emessages.proto\x12\tbasicswap\"\x84\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\"I\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\x62\x06proto3')
|
||||
)
|
||||
|
||||
|
||||
|
||||
_OFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor(
|
||||
name='LockType',
|
||||
full_name='basicswap.OfferMessage.LockType',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='NOT_SET', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='SEQUENCE_LOCK_BLOCKS', index=1, number=1,
|
||||
serialized_options=None,
|
||||
type=None),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='SEQUENCE_LOCK_TIME', index=2, number=2,
|
||||
serialized_options=None,
|
||||
type=None),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=345,
|
||||
serialized_end=418,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_OFFERMESSAGE_LOCKTYPE)
|
||||
|
||||
|
||||
_OFFERMESSAGE = _descriptor.Descriptor(
|
||||
name='OfferMessage',
|
||||
full_name='basicswap.OfferMessage',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='coin_from', full_name='basicswap.OfferMessage.coin_from', index=0,
|
||||
number=1, type=13, cpp_type=3, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='coin_to', full_name='basicswap.OfferMessage.coin_to', index=1,
|
||||
number=2, type=13, cpp_type=3, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='amount_from', full_name='basicswap.OfferMessage.amount_from', index=2,
|
||||
number=3, type=4, cpp_type=4, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='rate', full_name='basicswap.OfferMessage.rate', index=3,
|
||||
number=4, type=4, cpp_type=4, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='min_bid_amount', full_name='basicswap.OfferMessage.min_bid_amount', index=4,
|
||||
number=5, type=4, cpp_type=4, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='time_valid', full_name='basicswap.OfferMessage.time_valid', index=5,
|
||||
number=6, type=4, cpp_type=4, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='lock_type', full_name='basicswap.OfferMessage.lock_type', index=6,
|
||||
number=7, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='lock_value', full_name='basicswap.OfferMessage.lock_value', index=7,
|
||||
number=8, type=13, cpp_type=3, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='swap_type', full_name='basicswap.OfferMessage.swap_type', index=8,
|
||||
number=9, type=13, cpp_type=3, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='proof_address', full_name='basicswap.OfferMessage.proof_address', index=9,
|
||||
number=10, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='proof_signature', full_name='basicswap.OfferMessage.proof_signature', index=10,
|
||||
number=11, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='pkhash_seller', full_name='basicswap.OfferMessage.pkhash_seller', index=11,
|
||||
number=12, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='secret_hash', full_name='basicswap.OfferMessage.secret_hash', index=12,
|
||||
number=13, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
_OFFERMESSAGE_LOCKTYPE,
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=30,
|
||||
serialized_end=418,
|
||||
)
|
||||
|
||||
|
||||
_BIDMESSAGE = _descriptor.Descriptor(
|
||||
name='BidMessage',
|
||||
full_name='basicswap.BidMessage',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='offer_msg_id', full_name='basicswap.BidMessage.offer_msg_id', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='time_valid', full_name='basicswap.BidMessage.time_valid', index=1,
|
||||
number=2, type=4, cpp_type=4, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='amount', full_name='basicswap.BidMessage.amount', index=2,
|
||||
number=3, type=4, cpp_type=4, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='pkhash_buyer', full_name='basicswap.BidMessage.pkhash_buyer', index=3,
|
||||
number=4, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='proof_address', full_name='basicswap.BidMessage.proof_address', index=4,
|
||||
number=5, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='proof_signature', full_name='basicswap.BidMessage.proof_signature', index=5,
|
||||
number=6, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=421,
|
||||
serialized_end=561,
|
||||
)
|
||||
|
||||
|
||||
_BIDACCEPTMESSAGE = _descriptor.Descriptor(
|
||||
name='BidAcceptMessage',
|
||||
full_name='basicswap.BidAcceptMessage',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='bid_msg_id', full_name='basicswap.BidAcceptMessage.bid_msg_id', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='initiate_txid', full_name='basicswap.BidAcceptMessage.initiate_txid', index=1,
|
||||
number=2, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='contract_script', full_name='basicswap.BidAcceptMessage.contract_script', index=2,
|
||||
number=3, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b(""),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=563,
|
||||
serialized_end=649,
|
||||
)
|
||||
|
||||
_OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE
|
||||
_OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE
|
||||
DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), dict(
|
||||
DESCRIPTOR = _OFFERMESSAGE,
|
||||
__module__ = 'messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:basicswap.OfferMessage)
|
||||
))
|
||||
_sym_db.RegisterMessage(OfferMessage)
|
||||
|
||||
BidMessage = _reflection.GeneratedProtocolMessageType('BidMessage', (_message.Message,), dict(
|
||||
DESCRIPTOR = _BIDMESSAGE,
|
||||
__module__ = 'messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:basicswap.BidMessage)
|
||||
))
|
||||
_sym_db.RegisterMessage(BidMessage)
|
||||
|
||||
BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage', (_message.Message,), dict(
|
||||
DESCRIPTOR = _BIDACCEPTMESSAGE,
|
||||
__module__ = 'messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:basicswap.BidAcceptMessage)
|
||||
))
|
||||
_sym_db.RegisterMessage(BidAcceptMessage)
|
||||
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
123
basicswap/segwit_addr.py
Normal file
123
basicswap/segwit_addr.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# Copyright (c) 2017 Pieter Wuille
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
"""Reference implementation for Bech32 and segwit addresses."""
|
||||
|
||||
|
||||
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||
|
||||
|
||||
def bech32_polymod(values):
|
||||
"""Internal function that computes the Bech32 checksum."""
|
||||
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
||||
chk = 1
|
||||
for value in values:
|
||||
top = chk >> 25
|
||||
chk = (chk & 0x1ffffff) << 5 ^ value
|
||||
for i in range(5):
|
||||
chk ^= generator[i] if ((top >> i) & 1) else 0
|
||||
return chk
|
||||
|
||||
|
||||
def bech32_hrp_expand(hrp):
|
||||
"""Expand the HRP into values for checksum computation."""
|
||||
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
|
||||
|
||||
|
||||
def bech32_verify_checksum(hrp, data):
|
||||
"""Verify a checksum given HRP and converted data characters."""
|
||||
return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1
|
||||
|
||||
|
||||
def bech32_create_checksum(hrp, data):
|
||||
"""Compute the checksum values given HRP and data."""
|
||||
values = bech32_hrp_expand(hrp) + data
|
||||
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
|
||||
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
|
||||
|
||||
|
||||
def bech32_encode(hrp, data):
|
||||
"""Compute a Bech32 string given HRP and data values."""
|
||||
combined = data + bech32_create_checksum(hrp, data)
|
||||
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
|
||||
|
||||
|
||||
def bech32_decode(bech):
|
||||
"""Validate a Bech32 string, and determine HRP and data."""
|
||||
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
|
||||
(bech.lower() != bech and bech.upper() != bech)):
|
||||
return (None, None)
|
||||
bech = bech.lower()
|
||||
pos = bech.rfind('1')
|
||||
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
|
||||
return (None, None)
|
||||
if not all(x in CHARSET for x in bech[pos + 1:]):
|
||||
return (None, None)
|
||||
hrp = bech[:pos]
|
||||
data = [CHARSET.find(x) for x in bech[pos + 1:]]
|
||||
if not bech32_verify_checksum(hrp, data):
|
||||
return (None, None)
|
||||
return (hrp, data[:-6])
|
||||
|
||||
|
||||
def convertbits(data, frombits, tobits, pad=True):
|
||||
"""General power-of-2 base conversion."""
|
||||
acc = 0
|
||||
bits = 0
|
||||
ret = []
|
||||
maxv = (1 << tobits) - 1
|
||||
max_acc = (1 << (frombits + tobits - 1)) - 1
|
||||
for value in data:
|
||||
if value < 0 or (value >> frombits):
|
||||
return None
|
||||
acc = ((acc << frombits) | value) & max_acc
|
||||
bits += frombits
|
||||
while bits >= tobits:
|
||||
bits -= tobits
|
||||
ret.append((acc >> bits) & maxv)
|
||||
if pad:
|
||||
if bits:
|
||||
ret.append((acc << (tobits - bits)) & maxv)
|
||||
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
|
||||
return None
|
||||
return ret
|
||||
|
||||
|
||||
def decode(hrp, addr):
|
||||
"""Decode a segwit address."""
|
||||
hrpgot, data = bech32_decode(addr)
|
||||
if hrpgot != hrp:
|
||||
return (None, None)
|
||||
decoded = convertbits(data[1:], 5, 8, False)
|
||||
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
|
||||
return (None, None)
|
||||
if data[0] > 16:
|
||||
return (None, None)
|
||||
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
|
||||
return (None, None)
|
||||
return (data[0], decoded)
|
||||
|
||||
|
||||
def encode(hrp, witver, witprog):
|
||||
"""Encode a segwit address."""
|
||||
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
|
||||
if decode(hrp, ret) == (None, None):
|
||||
return None
|
||||
return ret
|
||||
292
basicswap/util.py
Normal file
292
basicswap/util.py
Normal file
@@ -0,0 +1,292 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2018-2019 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import os
|
||||
import decimal
|
||||
import subprocess
|
||||
import json
|
||||
import traceback
|
||||
import hashlib
|
||||
import urllib
|
||||
from xmlrpc.client import (
|
||||
Transport,
|
||||
Fault,
|
||||
)
|
||||
from .segwit_addr import bech32_decode, convertbits, bech32_encode
|
||||
|
||||
COIN = 100000000
|
||||
|
||||
|
||||
def format8(i):
|
||||
n = abs(i)
|
||||
quotient = n // COIN
|
||||
remainder = n % COIN
|
||||
rv = "%d.%08d" % (quotient, remainder)
|
||||
if i < 0:
|
||||
rv = '-' + rv
|
||||
return rv
|
||||
|
||||
|
||||
def toBool(s):
|
||||
return s.lower() in ["1", "true"]
|
||||
|
||||
|
||||
def dquantize(n, places=8):
|
||||
return n.quantize(decimal.Decimal(10) ** -places)
|
||||
|
||||
|
||||
def jsonDecimal(obj):
|
||||
if isinstance(obj, decimal.Decimal):
|
||||
return str(obj)
|
||||
raise TypeError
|
||||
|
||||
|
||||
def dumpj(jin, indent=4):
|
||||
return json.dumps(jin, indent=indent, default=jsonDecimal)
|
||||
|
||||
|
||||
def dumpje(jin):
|
||||
return json.dumps(jin, default=jsonDecimal).replace('"', '\\"')
|
||||
|
||||
|
||||
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
|
||||
def b58decode(v, length=None):
|
||||
long_value = 0
|
||||
for (i, c) in enumerate(v[::-1]):
|
||||
ofs = __b58chars.find(c)
|
||||
if ofs < 0:
|
||||
return None
|
||||
long_value += ofs * (58**i)
|
||||
result = bytes()
|
||||
while long_value >= 256:
|
||||
div, mod = divmod(long_value, 256)
|
||||
result = bytes((mod,)) + result
|
||||
long_value = div
|
||||
result = bytes((long_value,)) + result
|
||||
nPad = 0
|
||||
for c in v:
|
||||
if c == __b58chars[0]:
|
||||
nPad += 1
|
||||
else:
|
||||
break
|
||||
pad = bytes((0,)) * nPad
|
||||
result = pad + result
|
||||
if length is not None and len(result) != length:
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
def b58encode(v):
|
||||
long_value = 0
|
||||
for (i, c) in enumerate(v[::-1]):
|
||||
long_value += (256**i) * c
|
||||
|
||||
result = ''
|
||||
while long_value >= 58:
|
||||
div, mod = divmod(long_value, 58)
|
||||
result = __b58chars[mod] + result
|
||||
long_value = div
|
||||
result = __b58chars[long_value] + result
|
||||
|
||||
# leading 0-bytes in the input become leading-1s
|
||||
nPad = 0
|
||||
for c in v:
|
||||
if c == 0:
|
||||
nPad += 1
|
||||
else:
|
||||
break
|
||||
return (__b58chars[0] * nPad) + result
|
||||
|
||||
|
||||
def decodeWif(network_key):
|
||||
key = b58decode(network_key)[1:-4]
|
||||
if len(key) == 33:
|
||||
return key[:-1]
|
||||
return key
|
||||
|
||||
|
||||
def toWIF(prefix_byte, b, compressed=True):
|
||||
b = bytes((prefix_byte, )) + b
|
||||
if compressed:
|
||||
b += bytes((0x01, ))
|
||||
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
|
||||
return b58encode(b)
|
||||
|
||||
|
||||
def bech32Decode(hrp, addr):
|
||||
hrpgot, data = bech32_decode(addr)
|
||||
if hrpgot != hrp:
|
||||
return None
|
||||
decoded = convertbits(data, 5, 8, False)
|
||||
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
|
||||
return None
|
||||
return bytes(decoded)
|
||||
|
||||
|
||||
def bech32Encode(hrp, data):
|
||||
ret = bech32_encode(hrp, convertbits(data, 8, 5))
|
||||
if bech32Decode(hrp, ret) is None:
|
||||
return None
|
||||
return ret
|
||||
|
||||
|
||||
def decodeAddress(address_str):
|
||||
b58_addr = b58decode(address_str)
|
||||
if b58_addr is not None:
|
||||
address = b58_addr[:-4]
|
||||
checksum = b58_addr[-4:]
|
||||
assert(hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch'
|
||||
return b58_addr[:-4]
|
||||
return None
|
||||
|
||||
|
||||
def encodeAddress(address):
|
||||
checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()
|
||||
return b58encode(address + checksum[0:4])
|
||||
|
||||
|
||||
def getKeyID(bytes):
|
||||
data = hashlib.sha256(bytes).digest()
|
||||
return hashlib.new("ripemd160", data).digest()
|
||||
|
||||
|
||||
def pubkeyToAddress(prefix, pubkey):
|
||||
return encodeAddress(bytes((prefix,)) + getKeyID(pubkey))
|
||||
|
||||
|
||||
def SerialiseNum(n):
|
||||
if n == 0:
|
||||
return bytes([0x00])
|
||||
if n > 0 and n <= 16:
|
||||
return bytes([0x50 + n])
|
||||
rv = bytearray()
|
||||
neg = n < 0
|
||||
absvalue = -n if neg else n
|
||||
while(absvalue):
|
||||
rv.append(absvalue & 0xff)
|
||||
absvalue >>= 8
|
||||
if rv[-1] & 0x80:
|
||||
rv.append(0x80 if neg else 0)
|
||||
elif neg:
|
||||
rv[-1] |= 0x80
|
||||
return bytes([len(rv)]) + rv
|
||||
|
||||
|
||||
def DeserialiseNum(b, o=0):
|
||||
if b[o] == 0:
|
||||
return 0
|
||||
if b[o] > 0x50 and b[o] <= 0x50 + 16:
|
||||
return b[o] - 0x50
|
||||
v = 0
|
||||
nb = b[o]
|
||||
o += 1
|
||||
for i in range(0, nb):
|
||||
v |= b[o + i] << (8 * i)
|
||||
# If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative.
|
||||
if b[o + nb - 1] & 0x80:
|
||||
return -(v & ~(0x80 << (8 * (nb - 1))))
|
||||
return v
|
||||
|
||||
|
||||
class Jsonrpc():
|
||||
# __getattr__ complicates extending ServerProxy
|
||||
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
||||
allow_none=False, use_datetime=False, use_builtin_types=False,
|
||||
*, context=None):
|
||||
# establish a "logical" server connection
|
||||
|
||||
# get the url
|
||||
type, uri = urllib.parse.splittype(uri)
|
||||
if type not in ("http", "https"):
|
||||
raise OSError("unsupported XML-RPC protocol")
|
||||
self.__host, self.__handler = urllib.parse.splithost(uri)
|
||||
if not self.__handler:
|
||||
self.__handler = "/RPC2"
|
||||
|
||||
if transport is None:
|
||||
handler = Transport
|
||||
extra_kwargs = {}
|
||||
transport = handler(use_datetime=use_datetime,
|
||||
use_builtin_types=use_builtin_types,
|
||||
**extra_kwargs)
|
||||
self.__transport = transport
|
||||
|
||||
self.__encoding = encoding or 'utf-8'
|
||||
self.__verbose = verbose
|
||||
self.__allow_none = allow_none
|
||||
|
||||
def close(self):
|
||||
if self.__transport is not None:
|
||||
self.__transport.close()
|
||||
|
||||
def json_request(self, method, params):
|
||||
try:
|
||||
connection = self.__transport.make_connection(self.__host)
|
||||
headers = self.__transport._extra_headers[:]
|
||||
|
||||
request_body = {
|
||||
'method': method,
|
||||
'params': params,
|
||||
'id': 2
|
||||
}
|
||||
|
||||
connection.putrequest("POST", self.__handler)
|
||||
headers.append(("Content-Type", "application/json"))
|
||||
headers.append(("User-Agent", 'jsonrpc'))
|
||||
self.__transport.send_headers(connection, headers)
|
||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8'))
|
||||
|
||||
resp = connection.getresponse()
|
||||
return resp.read()
|
||||
|
||||
except Fault:
|
||||
raise
|
||||
except Exception:
|
||||
# All unexpected errors leave connection in
|
||||
# a strange state, so we clear it.
|
||||
self.__transport.close()
|
||||
raise
|
||||
|
||||
|
||||
def callrpc(rpc_port, auth, method, params=[], wallet=None):
|
||||
|
||||
try:
|
||||
url = 'http://%s@127.0.0.1:%d/' % (auth, rpc_port)
|
||||
if wallet:
|
||||
url += 'wallet/' + wallet
|
||||
x = Jsonrpc(url)
|
||||
|
||||
v = x.json_request(method, params)
|
||||
x.close()
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC Server Error')
|
||||
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
|
||||
return r['result']
|
||||
|
||||
|
||||
def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli'):
|
||||
cli_bin = os.path.join(bindir, cli_bin)
|
||||
|
||||
args = cli_bin + ('' if chain == 'mainnet' else ' -' + chain) + ' -datadir=' + datadir + ' ' + cmd
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
out = p.communicate()
|
||||
|
||||
if len(out[1]) > 0:
|
||||
raise ValueError('RPC error ' + str(out[1]))
|
||||
|
||||
r = out[0].decode('utf-8').strip()
|
||||
try:
|
||||
r = json.loads(r)
|
||||
except Exception:
|
||||
pass
|
||||
return r
|
||||
Reference in New Issue
Block a user