Skip to content

Commit

Permalink
Merge pull request #30 from deluxes/master
Browse files Browse the repository at this point in the history
Bech32 + P2SH Address support
metalicjames authored Jun 23, 2018
2 parents e69cf08 + ca71510 commit d2abd26
Showing 11 changed files with 221 additions and 45 deletions.
59 changes: 49 additions & 10 deletions p2pool/bitcoin/data.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
import warnings

import p2pool
from p2pool.util import math, pack
from p2pool.util import math, pack, segwit_addr

def hash256(data):
return pack.IntType(256).unpack(hashlib.sha256(hashlib.sha256(data).digest()).digest())
@@ -322,17 +322,31 @@ def base58_decode(b58data):
('pubkey_hash', pack.IntType(160)),
]))

def pubkey_hash_to_address(pubkey_hash, net):
return base58_encode(human_address_type.pack(dict(version=net.ADDRESS_VERSION, pubkey_hash=pubkey_hash)))
def pubkey_hash_to_address(pubkey_hash, net, version=None):
if version == None:
version = net.ADDRESS_VERSION

if version == 0:
return segwit_addr.encode(net.HUMAN_READABLE_PART, 0, [int(x) for x in bytearray.fromhex(hex(pubkey_hash)[2:-1])])
return base58_encode(human_address_type.pack(dict(version=version, pubkey_hash=pubkey_hash)))

def pubkey_to_address(pubkey, net):
return pubkey_hash_to_address(hash160(pubkey), net)

def address_to_pubkey_hash(address, net):
x = human_address_type.unpack(base58_decode(address))
if x['version'] != net.ADDRESS_VERSION:
raise ValueError('address not for this net!')
return x['pubkey_hash']
try:
base_decode = base58_decode(address)
x = human_address_type.unpack(base_decode)
if x['version'] != net.ADDRESS_VERSION and x['version'] != net.SEGWIT_ADDRESS_VERSION:
raise ValueError('address not for this net!')
return x['pubkey_hash'], x['version']
except Exception, e:
try:
hrp, pubkey_hash = segwit_addr.bech32_decode(address)
witver, witprog = segwit_addr.decode(net.HUMAN_READABLE_PART, address)
return int(''.join('{:02x}'.format(x) for x in witprog), 16), 0
except Exception, e:
raise ValueError('invalid addr')

# transactions

@@ -356,8 +370,12 @@ def pubkey_to_script2(pubkey):
assert len(pubkey) <= 75
return (chr(len(pubkey)) + pubkey) + '\xac'

def pubkey_hash_to_script2(pubkey_hash):
return '\x76\xa9' + ('\x14' + pack.IntType(160).pack(pubkey_hash)) + '\x88\xac'
def pubkey_hash_to_script2(pubkey_hash, version, net):
if version == 0:
return '\x00\x14' + hex(pubkey_hash)[2:-1].decode("hex")
if version == net.SEGWIT_ADDRESS_VERSION:
return ('\xa9\x14' + pack.IntType(160).pack(pubkey_hash)) + '\x87'
return '\x76\xa9' + ('\x14' + pack.IntType(160).pack(pubkey_hash)) + '\x88\xac'

def script2_to_address(script2, net):
try:
@@ -371,13 +389,31 @@ def script2_to_address(script2, net):

try:
pubkey_hash = pack.IntType(160).unpack(script2[3:-2])
script2_test2 = pubkey_hash_to_script2(pubkey_hash)
script2_test2 = pubkey_hash_to_script2(pubkey_hash, net.ADDRESS_VERSION, net)
except:
pass
else:
if script2_test2 == script2:
return pubkey_hash_to_address(pubkey_hash, net)

try:
pubkey_hash = int(script2[2:].encode('hex'), 16)
script2_test3 = pubkey_hash_to_script2(pubkey_hash, 0, net)
except:
pass
else:
if script2_test3 == script2:
return pubkey_hash_to_address(pubkey_hash, net, 0)

try:
pubkey_hash = pack.IntType(160).unpack(script2[2:-1])
script2_test4 = pubkey_hash_to_script2(pubkey_hash, net.SEGWIT_ADDRESS_VERSION, net)
except:
pass
else:
if script2_test4 == script2:
return pubkey_hash_to_address(pubkey_hash, net, net.SEGWIT_ADDRESS_VERSION)

def script2_to_human(script2, net):
try:
pubkey = script2[1:-1]
@@ -398,3 +434,6 @@ def script2_to_human(script2, net):
return 'Address. Address: %s' % (pubkey_hash_to_address(pubkey_hash, net),)

return 'Unknown. Script: %s' % (script2.encode('hex'),)

def is_segwit_script(script):
return script.startswith('\x00\x14') or script.startswith('\xa9\x14')
2 changes: 2 additions & 0 deletions p2pool/bitcoin/networks/vertcoin.py
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
P2P_PREFIX='fabfb5da'.decode('hex')
P2P_PORT=5889
ADDRESS_VERSION=71
SEGWIT_ADDRESS_VERSION=5
RPC_PORT=5888
RPC_CHECK=lambda bitcoind: True
SUBSIDY_FUNC=lambda height: 50*100000000 >> (height + 1)//840000
@@ -23,3 +24,4 @@
SANE_TARGET_RANGE=(2**256//1000000000000000000 - 1, 2**256//100000 - 1)
DUMB_SCRYPT_DIFF=16
DUST_THRESHOLD=0.03e8
HUMAN_READABLE_PART = 'vtc'
2 changes: 2 additions & 0 deletions p2pool/bitcoin/networks/vertcoin_testnet.py
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
P2P_PREFIX='76657274'.decode('hex')
P2P_PORT=15889
ADDRESS_VERSION=74
SEGWIT_ADDRESS_VERSION=196
RPC_PORT=15888
RPC_CHECK=lambda bitcoind: True
SUBSIDY_FUNC=lambda height: 50*100000000 >> (height + 1)//840000
@@ -23,3 +24,4 @@
SANE_TARGET_RANGE=(2**256//1000000000000000000 - 1, 2**256//100000 - 1)
DUMB_SCRYPT_DIFF=256
DUST_THRESHOLD=0.03e8
HUMAN_READABLE_PART = 'tvtc'
5 changes: 3 additions & 2 deletions p2pool/data.py
Original file line number Diff line number Diff line change
@@ -102,6 +102,7 @@ def get_dynamic_types(cls, net):
('coinbase', pack.VarStrType()),
('nonce', pack.IntType(32)),
('pubkey_hash', pack.IntType(160)),
('pubkey_hash_version', pack.IntType(8)),
('subsidy', pack.IntType(64)),
('donation', pack.IntType(16)),
('stale_info', pack.StaleInfoEnumType()),
@@ -193,7 +194,7 @@ def generate_transaction(cls, tracker, share_data, block_target, desired_timesta
assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight)

amounts = dict((script, share_data['subsidy']*(199*weight)//(200*total_weight)) for script, weight in weights.iteritems()) # 99.5% goes according to weights prior to this share
this_script = bitcoin_data.pubkey_hash_to_script2(share_data['pubkey_hash'])
this_script = bitcoin_data.pubkey_hash_to_script2(share_data['pubkey_hash'], share_data['pubkey_hash_version'], net.PARENT)
amounts[this_script] = amounts.get(this_script, 0) + share_data['subsidy']//200 # 0.5% goes to block finder
amounts[DONATION_SCRIPT] = amounts.get(DONATION_SCRIPT, 0) + share_data['subsidy'] - sum(amounts.itervalues()) # all that's left over is the donation weight and some extra satoshis due to rounding

@@ -303,7 +304,7 @@ def __init__(self, net, peer_addr, contents):
self.target = self.share_info['bits'].target
self.timestamp = self.share_info['timestamp']
self.previous_hash = self.share_data['previous_share_hash']
self.new_script = bitcoin_data.pubkey_hash_to_script2(self.share_data['pubkey_hash'])
self.new_script = bitcoin_data.pubkey_hash_to_script2(self.share_data['pubkey_hash'], self.share_data['pubkey_hash_version'], net.PARENT)
self.desired_version = self.share_data['desired_version']
self.absheight = self.share_info['absheight']
self.abswork = self.share_info['abswork']
29 changes: 16 additions & 13 deletions p2pool/main.py
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ def updatestamp(self, n):
def paytotal(self):
self.payouttotal = 0.0
for i in range(len(pubkeys.keys)):
self.payouttotal += node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(pubkeys.keys[i]), 0)*1e-8
self.payouttotal += node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(pubkeys.keys[i]['hash'], pubkeys.keys[i]['version'], net), 0)*1e-8
return self.payouttotal

def getpaytotal(self):
@@ -142,15 +142,17 @@ def poll_warnings():
with open(address_path, 'wb') as f:
f.write(address)

my_pubkey_hash = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
print ' ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT)
my_pubkey_hash, my_pubkey_hash_version = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
print ' ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT, my_pubkey_hash_version)
print
pubkeys.addkey(my_pubkey_hash)

pubkeys.addkey({'hash': my_pubkey_hash, 'version': my_pubkey_hash_version})
elif args.address != 'dynamic':
my_pubkey_hash = args.pubkey_hash
print ' ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT)
my_pubkey_hash_version = args.pubkey_hash_version
print ' ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT, my_pubkey_hash_version)
print
pubkeys.addkey(my_pubkey_hash)
pubkeys.addkey({'hash': my_pubkey_hash, 'version': my_pubkey_hash_version})
else:
print ' Entering dynamic address mode.'

@@ -159,15 +161,16 @@ def poll_warnings():
args.numaddresses = 2
for i in range(args.numaddresses):
address = yield deferral.retry('Error getting a dynamic address from bitcoind:', 5)(lambda: bitcoind.rpc_getnewaddress('p2pool'))()
new_pubkey = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
pubkeys.addkey(new_pubkey)
new_pubkey, pubkey_version = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
pubkeys.addkey({'hash': new_pubkey, 'version': pubkey_version})

pubkeys.updatestamp(time.time())

my_pubkey_hash = pubkeys.keys[0]
my_pubkey_hash = pubkeys.keys[0]['hash']
my_pubkey_hash_version = pubkeys.keys[0]['version']

for i in range(len(pubkeys.keys)):
print ' ...payout %d: %s' % (i, bitcoin_data.pubkey_hash_to_address(pubkeys.keys[i], net.PARENT),)
print ' ...payout %d: %s' % (i, bitcoin_data.pubkey_hash_to_address(pubkeys.keys[i]['hash'], net.PARENT, pubkeys.keys[i]['version']),)

print "Loading shares..."
shares = {}
@@ -292,7 +295,7 @@ def upnp_thread():
else:
share_rate_type = 'miner'
share_rate = args.miner_share_rate
wb = work.WorkerBridge(node, my_pubkey_hash, args.donation_percentage, merged_urls, args.worker_fee, args, pubkeys, bitcoind, args.min_difficulty, share_rate, share_rate_type)
wb = work.WorkerBridge(node, my_pubkey_hash, my_pubkey_hash_version, args.donation_percentage, merged_urls, args.worker_fee, args, pubkeys, bitcoind, args.min_difficulty, share_rate, share_rate_type)
web_root = web.get_web_root(wb, datadir_path, bitcoind_getinfo_var, static_dir=args.web_static)
caching_wb = worker_interface.CachingWorkerBridge(wb)
worker_interface.WorkerInterface(caching_wb).attach_to(web_root, get_handler=lambda request: request.redirect('/static/'))
@@ -406,7 +409,7 @@ def status_thread():
paystr = ''
paytot = 0.0
for i in range(len(pubkeys.keys)):
curtot = node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(pubkeys.keys[i]), 0)
curtot = node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(pubkeys.keys[i]['hash'], pubkeys.keys[i]['version'], net.PARENT), 0)
paytot += curtot*1e-8
paystr += "(%.4f)" % (curtot*1e-8,)
paystr += "=%.4f" % (paytot,)
@@ -633,7 +636,7 @@ def run():

if args.address is not None and args.address != 'dynamic':
try:
args.pubkey_hash = bitcoin_data.address_to_pubkey_hash(args.address, net.PARENT)
args.pubkey_hash, args.pubkey_hash_version = bitcoin_data.address_to_pubkey_hash(args.address, net.PARENT)
except Exception, e:
parser.error('error parsing address: ' + repr(e))
else:
1 change: 1 addition & 0 deletions p2pool/networks/vertcoin.py
Original file line number Diff line number Diff line change
@@ -18,3 +18,4 @@
VERSION_CHECK=lambda v: True
SOFTFORKS_REQUIRED = set(['nversionbips', 'csv', 'segwit'])
SEGWIT_ACTIVATION_VERSION = 16
MINIMUM_PROTOCOL_VERSION = 1800
1 change: 1 addition & 0 deletions p2pool/networks/vertcoin2.py
Original file line number Diff line number Diff line change
@@ -18,3 +18,4 @@
VERSION_CHECK=lambda v: True
SOFTFORKS_REQUIRED = set(['nversionbips', 'csv', 'segwit'])
SEGWIT_ACTIVATION_VERSION = 16
MINIMUM_PROTOCOL_VERSION = 1800
2 changes: 1 addition & 1 deletion p2pool/p2p.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ def fragment(f, **kwargs):
fragment(f, **dict((k, v[len(v)//2:]) for k, v in kwargs.iteritems()))

class Protocol(p2protocol.Protocol):
VERSION = 1700
VERSION = 1800

max_remembered_txs_size = 2500000

123 changes: 123 additions & 0 deletions p2pool/util/segwit_addr.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit d2abd26

Please sign in to comment.