Skip to content

Commit

Permalink
crypto - wallet, address, signature, verification
Browse files Browse the repository at this point in the history
  • Loading branch information
lukepark327 committed Nov 12, 2020
1 parent ca05de8 commit 226601f
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 22 deletions.
174 changes: 174 additions & 0 deletions crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"""
Ref: https://github.com/Destiner/blocksmith
"""
import time
import random
import secrets
import codecs
import hashlib
import ecdsa


class KeyGenerator:
def __init__(self):
self.POOL_SIZE = 256
self.KEY_BYTES = 32
self.CURVE_ORDER = int('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
self.pool = [0] * self.POOL_SIZE
self.pool_pointer = 0
self.prng_state = None
self.__init_pool()

def seed_input(self, str_input):
time_int = int(time.time())
self.__seed_int(time_int)
for char in str_input:
char_code = ord(char)
self.__seed_byte(char_code)

def generate_key(self):
big_int = self.__generate_big_int()
big_int = big_int % (self.CURVE_ORDER - 1) # key < curve order
big_int = big_int + 1 # key > 0
key = hex(big_int)[2:]
# Add leading zeros if the hex key is smaller than 64 chars
key = key.zfill(self.KEY_BYTES * 2)
return key

def __init_pool(self):
for i in range(self.POOL_SIZE):
random_byte = secrets.randbits(8)
self.__seed_byte(random_byte)
time_int = int(time.time())
self.__seed_int(time_int)

def __seed_int(self, n):
self.__seed_byte(n)
self.__seed_byte(n >> 8)
self.__seed_byte(n >> 16)
self.__seed_byte(n >> 24)

def __seed_byte(self, n):
self.pool[self.pool_pointer] ^= n & 255
self.pool_pointer += 1
if self.pool_pointer >= self.POOL_SIZE:
self.pool_pointer = 0

def __generate_big_int(self):
if self.prng_state is None:
seed = int.from_bytes(self.pool, byteorder='big', signed=False)
random.seed(seed)
self.prng_state = random.getstate()
random.setstate(self.prng_state)
big_int = random.getrandbits(self.KEY_BYTES * 8)
self.prng_state = random.getstate()
return big_int


class BitcoinWallet:
@staticmethod
def generate_address(private_key):
public_key = BitcoinWallet.private_to_public(private_key)
address = BitcoinWallet.public_to_address(public_key)
return address

@staticmethod
def generate_compressed_address(private_key):
public_key = BitcoinWallet.private_to_compressed_public(private_key)
address = BitcoinWallet.public_to_address(public_key)
return address

@staticmethod
def private_to_public(private_key):
private_key_bytes = codecs.decode(private_key, 'hex')
# Get ECDSA public key
key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
key_hex = codecs.encode(key_bytes, 'hex')
# Add bitcoin byte
bitcoin_byte = b'04'
public_key = bitcoin_byte + key_hex
return public_key

@staticmethod
def private_to_compressed_public(private_key):
private_hex = codecs.decode(private_key, 'hex')
# Get ECDSA public key
key = ecdsa.SigningKey.from_string(private_hex, curve=ecdsa.SECP256k1).verifying_key
key_bytes = key.to_string()
key_hex = codecs.encode(key_bytes, 'hex')
# Get X from the key (first half)
key_string = key_hex.decode('utf-8')
half_len = len(key_hex) // 2
key_half = key_hex[:half_len]
# Add bitcoin byte: 0x02 if the last digit is even, 0x03 if the last digit is odd
last_byte = int(key_string[-1], 16)
bitcoin_byte = b'02' if last_byte % 2 == 0 else b'03'
public_key = bitcoin_byte + key_half
return public_key

@staticmethod
def public_to_address(public_key):
public_key_bytes = codecs.decode(public_key, 'hex')
# Run SHA256 for the public key
sha256_bpk = hashlib.sha256(public_key_bytes)
sha256_bpk_digest = sha256_bpk.digest()
# Run ripemd160 for the SHA256
ripemd160_bpk = hashlib.new('ripemd160')
ripemd160_bpk.update(sha256_bpk_digest)
ripemd160_bpk_digest = ripemd160_bpk.digest()
ripemd160_bpk_hex = codecs.encode(ripemd160_bpk_digest, 'hex')
# Add network byte
network_byte = b'00'
network_bitcoin_public_key = network_byte + ripemd160_bpk_hex
network_bitcoin_public_key_bytes = codecs.decode(network_bitcoin_public_key, 'hex')
# Double SHA256 to get checksum
sha256_nbpk = hashlib.sha256(network_bitcoin_public_key_bytes)
sha256_nbpk_digest = sha256_nbpk.digest()
sha256_2_nbpk = hashlib.sha256(sha256_nbpk_digest)
sha256_2_nbpk_digest = sha256_2_nbpk.digest()
sha256_2_hex = codecs.encode(sha256_2_nbpk_digest, 'hex')
checksum = sha256_2_hex[:8]
# Concatenate public key and checksum to get the address
address_hex = (network_bitcoin_public_key + checksum).decode('utf-8')
wallet = base58(address_hex)
return wallet


def base58(address_hex):
alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
b58_string = ''
# Get the number of leading zeros and convert hex to decimal
leading_zeros = len(address_hex) - len(address_hex.lstrip('0'))
# Convert hex to decimal
address_int = int(address_hex, 16)
# Append digits to the start of string
while address_int > 0:
digit = address_int % 58
digit_char = alphabet[digit]
b58_string = digit_char + b58_string
address_int //= 58
# Add '1' for each 2 leading zeros
ones = leading_zeros // 2
for one in range(ones):
b58_string = '1' + b58_string
return b58_string


if __name__ == "__main__":
# Generate Private Key
kg = KeyGenerator()
private_key = kg.generate_key()
print("Private Key\t:", end=' ')
print(private_key)

# Private Key -> Public Key
# public_key = BitcoinWallet.private_to_public(private_key)
public_key = BitcoinWallet.private_to_compressed_public(private_key)
print("Public Key\t:", end=' ')
print(public_key)

# Public Key -> Address
address = BitcoinWallet.public_to_address(public_key)
print("Address\t\t:", end=' ')
print(address)
54 changes: 45 additions & 9 deletions ds.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from time import time
from copy import deepcopy
from hashlib import sha256
from ecdsa import VerifyingKey, SECP256k1


class Transaction():
Expand All @@ -11,19 +12,22 @@ def __init__(
sender: str,
receiver: str,
amount: int,
data: str = None):
data: str = None,
sign=None):

self.sender = sender
self.receiver = receiver
self.amount = amount
self.data = data
self.sign = sign

def toDict(self):
return {
'sender': self.sender,
'receiver': self.receiver,
'amount': self.amount,
'data': self.data
'data': self.data,
'sign': self.sign
}


Expand Down Expand Up @@ -86,12 +90,15 @@ def __init__(self):
self.transaction_pool = []

def init_genesis_block(self):

# TODO: re-calculate nonce

with open('./genesis.json') as f:
raw = json.load(f)

txs = []
for tx in raw['transactions']:
txs.append(Transaction(tx['sender'], tx['receiver'], tx['amount'], tx['data']))
txs.append(Transaction(tx['sender'], tx['receiver'], tx['amount'], tx['data'], tx['sign']))

return self.new_block(raw['index'], raw['timestamp'], raw['prev_hash'], raw['nonce'], txs)

Expand All @@ -104,13 +111,13 @@ def mine_block(
nonce=None,
transactions=None
):
mining_tx = Transaction("0x0", miner, 50)
mining_tx = Transaction("", miner, 50)
if transactions:
txs = deepcopy(transactions)
else:
txs = deepcopy(self.transaction_pool)
self.transaction_pool = []
txs.append(mining_tx)
txs.insert(0, mining_tx)

mined_block = self.new_block(index, timestamp, prev_hash, nonce, txs)
self.chain.append(mined_block)
Expand Down Expand Up @@ -145,8 +152,14 @@ def find_nonce(self, block):
print('>>> nonce: %10d' % (block.header.nonce), '\t', 'hash: ', guess, end='\r')
return guess[:4] == "0000" # N is 4

def new_transaction(self, sender, recipient, amount, data=None):
tx = Transaction(sender, recipient, amount, data)
def new_transaction(self, sk, sender, receiver, amount, data=None):
tx = Transaction(sender, receiver, amount, data)

# sign
tx_string = json.dumps(tx.toDict(), sort_keys=True).encode()
tx_digest = sha256(tx_string).digest()
tx.sign = sk.sign(tx_digest).hex()

self.transaction_pool.append(tx)
return self.last_block.header.index + 1

Expand Down Expand Up @@ -179,12 +192,20 @@ def valid_block(self, block, prev_block):
# Valid index & prev_hash
if block.header.index == 0:
if block.hash() != self.init_genesis_block().hash():
print(block.toDict())
print(self.init_genesis_block().toDict())
return False
else:
if prev_block.hash() != block.header.prev_hash:
return False

# TODO: Valid transactions
# Valid transactions
for tx in block.body:
if block.header.index == 0:
pass # TODO: re-calculate genesis' tx sign.
else:
self.valid_transaction(tx)

# TODO: Valid timestamp

# Valid nonce
Expand All @@ -195,4 +216,19 @@ def valid_block(self, block, prev_block):
return True

def valid_transaction(self, tx):
pass
if tx.sender == '':
# mint
pass # TODO: minting addr.
else:
raw_tx = Transaction(tx.sender, tx.receiver, tx.amount, tx.data)
raw_tx_string = json.dumps(raw_tx.toDict(), sort_keys=True).encode()
raw_tx_digest = sha256(raw_tx_string).digest()

vk = VerifyingKey.from_string(bytearray.fromhex(tx.sender), curve=SECP256k1)
return vk.verify(bytes(bytearray.fromhex(tx.sign)), raw_tx_digest)


if __name__ == "__main__":
bc = Blockchain()
g = bc.init_genesis_block()
print(bc.proof_of_work(g.header.index, g.header.timestamp, g.header.prev_hash, g.body))
9 changes: 5 additions & 4 deletions genesis.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"index": 0,
"timestamp": 1603181003,
"prev_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": 295131,
"prev_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"nonce": 131966,
"transactions": [
{
"sender": "",
"receiver": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"receiver": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": 50,
"data": "Hello, Blockchain World!\n"
"data": "Hello, Blockchain World!\n",
"sign": "af85d3a07402bcf8c6819713e611bf328de73c1b978580c8d6c83418f1c64671a3a87670c2499bc4781305cf0615edb186608a63943abc8d26a1c619a642da8a"
}
]
}
Loading

0 comments on commit 226601f

Please sign in to comment.