Skip to content

Commit

Permalink
Update address
Browse files Browse the repository at this point in the history
  • Loading branch information
esneider committed May 18, 2015
1 parent 4a826f3 commit 0abd0b6
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 85 deletions.
157 changes: 86 additions & 71 deletions bitforge/address.py
Original file line number Diff line number Diff line change
@@ -1,120 +1,135 @@
import binascii, collections
from enum import Enum
import collections

import network, utils
from network import Network
from encoding import *
from errors import *
# from script import Script
import enum

from bitforge import encoding, error, network

BaseAddress = collections.namedtuple('Address', ['phash', 'network', 'type'])

class Address(BaseAddress):
class Type(enum.Enum):
PublicKey = 'pubkey_hash'
Script = 'script_hash'


class Error(error.BitforgeError):
pass


class Type(Enum):
PublicKey = 'pubkeyhash'
Script = 'scripthash'
class InvalidEncoding(Error):
pass

class Error(BitforgeError):
pass

class UnknownNetwork(Error, Network.UnknownNetwork):
"No network for Address with an attribute '{key}' of value {value}"
class InvalidHashLength(Error, error.StringError):
"The address hash {string} should be 20 bytes long, not {length}"

class InvalidVersion(Error, NumberError):
"Failed to detect Address type and network from version number {number}"

class InvalidBase58h(Error, InvalidBase58h):
"The Address string {string} is not valid base58/check"
class InvalidType(Error, error.ObjectError):
"{object} is not a valid address type"

class InvalidHex(Error, InvalidHex):
"The Address string {string} is not valid hexadecimal"

class InvalidHashLength(Error, StringError):
"The address hash {string} should be 20 bytes long, not {length}"
BaseAddress = collections.namedtuple('Address', [
'hash',
'type',
'network',
])

class InvalidBinaryLength(Error, StringError):
"The binary address {string} should be 21 bytes long, not {length}"

class InvalidType(Error, ObjectError):
"Address type {object} is not an instance of Address.Type"
class Address(BaseAddress):
"""Bitcoin address."""

def __new__(cls, hash, type=Type.PublicKey, network=network.default):
"""TODO"""

if not isinstance(type, Type):
raise InvalidType(type)

def __new__(cls, phash, network = network.default, type = Type.PublicKey):
if not isinstance(type, Address.Type):
raise Address.InvalidType(type)
if len(hash) != 20:
raise InvalidHashLength(hash)

if len(phash) != 20:
raise Address.InvalidHashLength(phash)
return super(Address, cls).__new__(cls, hash, type, network)

return super(Address, cls).__new__(cls, phash, network, type)
@classmethod
def from_string(cls, string):
"""TODO"""

@staticmethod
def from_string(string):
try:
bytes = decode_base58h(string)
except InvalidBase58h:
raise Address.InvalidBase58h(string)
data = encoding.a2b_base58check(string)

except encoding.InvalidEncoding as e:
raise InvalidEncoding(e.message)

return cls.from_bytes(data)

return Address.from_bytes(bytes)
@classmethod
def from_bytes(cls, data):
"""TODO"""

@staticmethod
def from_bytes(bytes):
if len(bytes) != 21:
raise Address.InvalidBinaryLength(bytes)
if len(data) != 21:
raise InvalidEncoding('Invalid address length')

network, type = Address.classify_bytes(bytes)
type_, network_ = cls.classify_bytes(data)

return Address(bytes[1:], network, type)
return cls(data[1:], type_, network_)

@classmethod
def from_hex(cls, string):
"""TODO"""

@staticmethod
def from_hex(string):
try:
bytes = decode_hex(string)
except InvalidHex:
raise Address.InvalidHex(string)
data = encoding.a2b_hex(string)

except encoding.InvalidEncoding as e:
raise InvalidEncoding(e.message)

return cls.from_bytes(data)

@classmethod
def classify_bytes(cls, data):
"""TODO"""

return Address.from_bytes(bytes)
data = bytearray(data)
version = data[0]

@staticmethod
def classify_bytes(bytes):
version = decode_int(bytes[0])
network_ = network.Network.get_by_field('pubkey_hash_prefix', version, raises=False)
if network_ is not None:
return (Type.PublicKey, network_)

network = Network.get_by_field('pubkeyhash', version, raises = False)
if network is not None:
return (network, Address.Type.PublicKey)
network_ = network.Network.get_by_field('script_hash_prefix', version, raises=False)
if network_ is not None:
return (Type.Script, network_)

network = Network.get_by_field('scripthash', version, raises = False)
if network is not None:
return (network, Address.Type.Script)
raise InvalidEncoding('Invalid version number')

raise Address.InvalidVersion(version)
@classmethod
def from_public_key(cls, pubkey):
"""TODO"""

@staticmethod
def from_public_key(pubkey):
phash = ripemd160(sha256(pubkey.to_bytes()))
return Address(phash, pubkey.network, Address.Type.PublicKey)
return pubkey.address()

def to_bytes(self):
"""TODO"""

version = getattr(self.network, self.type.value)
return chr(version) + self.phash
return chr(version) + self.hash

def to_string(self):
return encode_base58h(self.to_bytes())
"""TODO"""

return encoding.b2a_base58check(self.to_bytes())

def to_hex(self):
return encode_hex(self.to_bytes())
"""TODO"""

return encoding.b2a_hex(self.to_bytes())

# TODO: all keys should be from the same network
# @staticmethod
# @classmethod
# def from_public_keys(pubkeys, threshold):
# return Address.from_script(
# Script.buildMultisigOut(pubkeys, threshold),
# pubkeys[0].network
# )

# @staticmethod
# @classmethod
# def from_script(script, network = networks.default):
# if not isinstance(script, Script):
# raise ValueError('Expected instance of Script, not %s' % script)
Expand Down
31 changes: 22 additions & 9 deletions bitforge/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class InvalidField(Error, error.StringError):
'hash_function', # Signature hashing function

# Serialization magic numbers
'pubkeyhash',
'wif_prefix', # Byte prefix used to identify the network in the WIF encoding
'scripthash',
'pubkey_hash_prefix', # Byte prefix used for P2PKH addresses
'script_hash_prefix', # Byte prefix used for P2SH addresses
'hd_public_key',
'hd_private_key',
'magic',
Expand All @@ -50,7 +50,12 @@ class InvalidField(Error, error.StringError):
class Network(BaseNetwork):
"""Parameters of a Bitcoin-compatible network."""

UNIQUE_FIELDS = ['name', 'wif_prefix']
UNIQUE_FIELDS = [
'name',
'wif_prefix',
'pubkey_hash_prefix',
'script_hash_prefix',
]

_networks = []
_networks_by_name = {}
Expand Down Expand Up @@ -91,6 +96,14 @@ def __new__(cls, **kwargs):
if getattr(other, field) == getattr(network, field):
raise InvalidNetwork(field, getattr(network, field))

# Enforce uniqueness of the address prefixes
for other in cls._networks:
if other.pubkey_hash_prefix == network.script_hash_prefix:
raise InvalidNetwork('address prefix', network.script_hash_prefix)

if other.script_hash_prefix == network.pubkey_hash_prefix:
raise InvalidNetwork('address prefix', network.pubkey_hash_prefix)

cls._networks.append(network)

# Enforce uniqueness of the network aliases
Expand All @@ -112,10 +125,10 @@ def __str__(self):
name = 'testnet',
aliases = [],
curve = ec.SECP256K1(),
hash_function = hashes.SHA256,
pubkeyhash = 111,
hash_function = hashes.SHA256(),
wif_prefix = 239,
scripthash = 196,
pubkey_hash_prefix = 111,
script_hash_prefix = 196,
hd_public_key = 0x043587cf,
hd_private_key = 0x04358394,
magic = 0x0b110907,
Expand All @@ -133,10 +146,10 @@ def __str__(self):
name = 'livenet',
aliases = ['mainnet', 'default'],
curve = ec.SECP256K1(),
hash_function = hashes.SHA256,
pubkeyhash = 0x00,
hash_function = hashes.SHA256(),
wif_prefix = 0x80,
scripthash = 0x05,
pubkey_hash_prefix = 0x00,
script_hash_prefix = 0x05,
hd_public_key = 0x0488b21e,
hd_private_key = 0x0488ade4,
magic = 0xf9beb4d9,
Expand Down
19 changes: 14 additions & 5 deletions bitforge/pubkey.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import collections

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec

from bitforge import encoding, error, network, tools
from bitforge import address, encoding, error, network, tools


# Magic numbers for the SEC1 public key format (TODO: shouldn't be here!)
Expand Down Expand Up @@ -111,10 +112,18 @@ def from_bytes(cls, data, network=network.default, backend=default_backend()):

return cls.from_point(x, y, network, compressed, backend)

# def address(self):
# """TODO"""
#
# return Address.from_public_key(self)
def address(self, backend=default_backend()):
"""TODO"""

SHA256 = hashes.Hash(hashes.SHA256(), backend)
SHA256.update(self.to_bytes())

RIPEMD160 = hashes.Hash(hashes.RIPEMD160, backend)
RIPEMD160.update(SHA256.finalize())

digest = RIPEMD160.finalize()

return address.Address(digest, self.network, address.Type.PublicKey)

def to_bytes(self):
"""TODO"""
Expand Down

0 comments on commit 0abd0b6

Please sign in to comment.