def make_miner_keys(): """ makes a public-private key pair that the miner will use to receive the mining reward and the transaction fee for each transaction. This function writes the keys to a file and returns hash: RIPEMD160(SHA256(public key)) """ try: keys = rcrypt.make_ecc_keys() privkey = keys[0] pubkey = keys[1] pkhash = rcrypt.make_SHA256_hash(pubkey) mdhash = rcrypt.make_RIPEMD160_hash(pkhash) # write the keys to file with the private key as a hexadecimal string f = open('coinbase_keys.txt', 'a') f.write(privkey) f.write('\n') # newline f.write(pubkey) f.write('\n') f.close() except Exception as err: logging.debug('make_miner_keys: exception: ' + str(err)) return mdhash
def test_make_coinbase_transaction(): """ tests making a coinbase transaction """ ctx = hmining.make_coinbase_transaction(10, "synthetic pubkey") assert len(ctx["vin"]) == 0 assert len(ctx["vout"]) == 1 hash = rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash("synthetic pubkey")) assert ctx["vout"][0]["ScriptPubKey"][1] == hash
def unlock_transaction_fragment(vinel: "dictionary", fragment: "dictionary") -> "boolean": """ unlocks a previous transaction fragment using the p2pkhash script. Executes the script and returns False if the transaction is not unlocked Receives: the consuming vin and the previous transaction fragment consumed by the vin element. """ try: execution_stack = [] result_stack = [] # make the execution stack for a p2pkhash script # since we only have one type of script in Helium # we can use hard-coded values execution_stack.append('SIG') execution_stack.append('PUBKEY') execution_stack.append('<DUP>') execution_stack.append('<HASH_160>') execution_stack.append('HASH-160') execution_stack.append('<EQ-VERIFY>') execution_stack.append('<CHECK_SIG>') # Run the p2pkhash execution stack result_stack.insert(0, vinel['ScriptSig'][0]) result_stack.insert(0, vinel['ScriptSig'][1]) result_stack.insert(0, vinel['ScriptSig'][1]) hash_160 = rcrypt.make_SHA256_hash(vinel['ScriptSig'][1]) hash_160 = rcrypt.make_RIPEMD160_hash(hash_160) result_stack.insert(0, hash_160) result_stack.insert(0, fragment["pkhash"]) # do the EQ_VERIFY operation tmp1 = result_stack.pop(0) tmp2 = result_stack.pop(0) # test for RIPEMD-160 hash match if tmp1 != tmp2: raise (ValueError("public key match failure")) # test for a signature match ret = rcrypt.verify_signature(vinel['ScriptSig'][1], vinel['ScriptSig'][1], vinel['ScriptSig'][0]) if ret == False: raise (ValueError("signature match failure")) except Exception as err: logging.debug('unlock_transaction_fragment: exception: ' + str(err)) return False return True
def make_synthetic_vout(block, trx, vout_index, pubkey): """ make a randomized vout element receives a public key returns the vout dict element """ vout = {} # genesis transaction # spendable values if block["height"] == 0: vout['value'] = secrets.randbelow( 10_000_000_000_000) + 10 # helium cents else: value_received = 0 ctr = 0 for vin in trx["vin"]: fragmentid = vin["txid"] + "_" + str(ctr) fragment = hchaindb.get_transaction(fragmentid) if fragment == False: break value_received += fragment["value"] ctr += 1 if value_received < 0: raise (ValueError("negative value receive by vin")) if value_received == 0: return False value_spent = 0 for vout in trx["vout"]: value_spent += vout["value"] value_available = value_received - value_spent if value_available < 0: raise (ValueError("funds available are negative")) if value_available == 0: return False vout['value'] = secrets.randbelow(value_available) + 1000 ripemd_hash = rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(pubkey)) tmp = [] tmp.append('<DUP>') tmp.append('<HASH-160>') tmp.append(ripemd_hash) tmp.append('<EQ-VERIFY>') tmp.append('<CHECK-SIG>') vout["ScriptPubKey"] = [] vout['ScriptPubKey'].append(tmp) #add fragment to wallet fragment["transactionid"] = trx["transactionid"] fragment["vout_index"] = vout_index wallet.append(fragment) return vout
def test_pubkeyhash(): """ test a public key hash in scriptpubkey for a valid RIPEMD-160 format """ txn = make_synthetic_transaction(5) indices = randomize_list(txn['vout']) for ctr in list(range(len(indices))): val = txn['vout'][indices[ctr]]['ScriptPubKey'][2] val = rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(val)) assert rcrypt.validate_RIPEMD160_hash(val) == True
def value_received(blockno:"integer"= 0) -> "list" : """ obtains all of the helium values received by the wallet-holder by examining transactions in the blockchain from and including blockno onwards. Updates the wallet state. """ hreceived = [] tmp = {} try: # get values received from the blockchain for block in hblockchain.blockchain: if block["height"] < blockno: continue for transaction in block["tx"]: ctr = -1 for vout in transaction["vout"]: ctr += 1 for key_pair in wallet_state["keys"]: if rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(key_pair[1])) \ == vout["ScriptPubKey"][2]: tmp["value"] = vout["value"] tmp["blockno"] = block["height"] tmp["fragmentid"] = transaction["transactionid"] + "_" + str(ctr) tmp["public_key"] = key_pair[1] hreceived.append(tmp) break # update the wallet state last_block = hblockchain.blockchain[-1] if last_block["height"] > wallet_state["received_last_block_scanned"]: wallet_state["received_last_block_scanned"] = last_block["height"] for received in hreceived: wallet_state["received"].append(received) except Exception as err: print(str(err)) logging.debug('value_received exception: ' + str(err)) return False return
def value_spent(blockno:"integer"= 0): """ obtains all of the helium values transferred by the wallet-holder by examining transactions in the blockchain from and including blockno. onwards. Updates the wallet state. """ hspent = [] tvalue = {} try: # get values spent from blockchain transactions for block in hblockchain.blockchain: if block["height"] < blockno: continue for transaction in block["tx"]: for vin in transaction["vin"]: for key_pair in wallet_state["keys"]: prevtxid = vin["transactionid"] + "_" + str(vin["vout_index"]) fragment = hchaindb.get_transaction(prevtxid) if rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(key_pair[1])) \ == fragment["pkhash"] : tvalue["value"] = vin["value"] tvalue["blockno"] = block["height"] tvalue["fragmentid"] = vin["transactionid"] + "_" \ + str(vin["vout_index"]) tvalue["public_key"] = key_pair[1] hspent.append(tvalue) break last_block = hblockchain.blockchain[-1] if last_block["height"] > wallet_state["spent_last_block_scanned"]: wallet_state["spent_last_block_scanned"] = last_block["height"] for spent in hspent: wallet_state["spent"].append(spent) except Exception as err: print(str(err)) logging.debug('value_sent: exception: ' + str(err)) return False return
def test_unlock_bad_signature(monkeypatch): """ test unlocking a transaction with a bad signature """ global prev_tx_keys prev_tx_keys.clear() txn1 = make_synthetic_previous_transaction(4) txn2 = make_synthetic_transaction(2) # make a transaction fragment where the first vin element # of txn2 consumes the value of the first vout element of txn1 # synthetic consuming vin element in tx2 vin = {} vin['txid'] = txn1["transactionid"] vin['vout_index'] = 0 vin['ScriptSig'] = {} # use wrong private key to sign key_pair = rcrypt.make_ecc_keys() signature = rcrypt.sign_message(key_pair[0], prev_tx_keys[1][1]) pubkey = prev_tx_keys[1][1] sig = [] sig.append(signature) sig.append(pubkey + "corrupted") vin['ScriptSig'] = sig # public key hash in txn2 ripemd_hash = rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(prev_tx_keys[1][1])) fragment = { "value": 210, "pkhash": ripemd_hash, "spent": False, "tx_chain": txn2["transactionid"] + "_" + "0", "checksum": rcrypt.make_SHA256_hash(txn1["transactionid"]) } assert tx.unlock_transaction_fragment(vin, fragment) == False
def make_coinbase_transaction(block_height: "integer", pubkey: "string") -> 'dict': """ makes a coinbase transaction, this is the miner's reward for mining a block. Receives a public key to denote ownership of the reward. Since this is a fresh issuance of heliums there are no vin elements. locks the transaction for hconfig["COINBASE_INTERVAL"] blocks. Returns the coinbase transaction. """ try: # calculate the mining reward reward = mining_reward(block_height) # create a coinbase transaction trx = {} trx['transactionid'] = rcrypt.make_uuid() trx['version'] = hconfig.conf["VERSION_NO"] # the mining reward cannot be claimed until approximately 100 blocks are mined # convert into a time interval trx['locktime'] = hconfig.conf["COINBASE_INTERVAL"] * 600 trx['vin'] = [] trx['vout'] = [] ScriptPubKey = [] ScriptPubKey.append('SIG') ScriptPubKey.append( rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(pubkey))) ScriptPubKey.append('<DUP>') ScriptPubKey.append('<HASH_160>') ScriptPubKey.append('HASH-160') ScriptPubKey.append('<EQ-VERIFY>') ScriptPubKey.append('<CHECK_SIG>') # create the vout element with the reward trx['vout'].append({'value': reward, 'ScriptPubKey': ScriptPubKey}) except Exception as err: logging.debug('make_coinbase_transaction: exception: ' + str(err)) return trx
def add_transaction_fee(trx: 'dictionary', pubkey: 'string') -> 'dictionary': """ add_transaction_fee directs the transaction fee of a transaction to the miner. receives a transaction and a miner's public key. amends and returns the transaction so that it consumes the transaction fee. """ try: # get the previous transaction fragments prev_fragments = [] for vin in trx["vin"]: fragment_id = vin["txid"] + "_" + str(vin["vout_index"]) prev_fragments.append(hchaindb.get_transaction(fragment_id)) # Calculate the transaction fee fee = tx.transaction_fee(trx, prev_fragments) if fee > 0: vout = {} vout["value"] = fee ScriptPubKey = [] ScriptPubKey.append('SIG') ScriptPubKey.append( rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(pubkey))) ScriptPubKey.append('<DUP>') ScriptPubKey.append('<HASH_160>') ScriptPubKey.append('HASH-160') ScriptPubKey.append('<EQ-VERIFY>') ScriptPubKey.append('<CHECK_SIG>') vout["ScriptPubKey"] = ScriptPubKey trx["vout"].append(vout) except Exception as err: logging.debug('add_transaction_fee: exception: ' + str(err)) return False return trx
def make_random_transaction(blockno, is_coinbase): txn = {} txn["version"] = "1" txn["transactionid"] = rcrypt.make_uuid() if is_coinbase == True: txn["locktime"] = hconfig.conf["COINBASE_LOCKTIME"] else: txn["locktime"] = 0 # the public-private key pair for this transaction transaction_keys = rcrypt.make_ecc_keys() # previous transaction fragments spent by this transaction total_spendable = 0 ####################### # Build the vin array ####################### txn["vin"] = [] # genesis block transactions have no prior inputs. # coinbase transactions do not have any inputs if (blockno > 0) and (is_coinbase != True): max_inputs = secrets.randbelow(hconfig.conf["MAX_INPUTS"]) if max_inputs == 0: max_inputs = hconfig.conf["MAX_INPUTS"] - 1 # get some random previous unspent transaction # fragments to spend ind = 0 ctr = 0 while ind < max_inputs: # get a random unspent fragment from a previous block index = secrets.randbelow(len(unspent_fragments)) frag_dict = unspent_fragments[index] key = [*frag_dict.keys()][0] val = [*frag_dict.values()][0] if val["blockno"] == blockno: ctr += 1 if ctr == 10000: print("failed to get random unspent fragment") return False continue unspent_fragments.pop(index) total_spendable += val["value"] tmp = hchaindb.get_transaction(key) if tmp == False: print("cannot get fragment from chainstate: " + key) assert tmp != False assert tmp["spent"] == False assert tmp["value"] > 0 # create a random vin element key_array = key.split("_") signed = rcrypt.sign_message(val["privkey"], val["pubkey"]) ScriptSig = [] ScriptSig.append(signed) ScriptSig.append(val["pubkey"]) txn["vin"].append({ "txid": key_array[0], "vout_index": int(key_array[1]), "ScriptSig": ScriptSig }) ctr = 0 ind += 1 ##################### # Build Vout list ##################### txn["vout"] = [] # genesis block if blockno == 0: total_spendable = secrets.randbelow(10_000_000) + 50_000 # we need at least one transaction output for non-coinbase # transactions if is_coinbase == True: max_outputs = hconfig.conf["MAX_OUTPUTS"] else: max_outputs = secrets.randbelow(hconfig.conf["MAX_OUTPUTS"]) if max_outputs <= 1: max_outputs = 2 ind = 0 while ind < max_outputs: tmp = rcrypt.make_SHA256_hash(transaction_keys[1]) tmp = rcrypt.make_RIPEMD160_hash(tmp) ScriptPubKey = [] ScriptPubKey.append("<DUP>") ScriptPubKey.append("<HASH-160>") ScriptPubKey.append(tmp) ScriptPubKey.append("<EQ-VERIFY>") ScriptPubKey.append("<CHECK-SIG>") if is_coinbase == True: value = hmining.mining_reward(blockno) else: amt = int(total_spendable / max_outputs) value = secrets.randbelow(amt) # helium cents if value == 0: value = int(amt / 10) total_spendable -= value assert value > 0 assert total_spendable >= 0 txn["vout"].append({"value": value, "ScriptPubKey": ScriptPubKey}) # save the transaction fragment fragid = txn["transactionid"] + "_" + str(ind) fragment = {} fragment[fragid] = { "value": value, "privkey": transaction_keys[0], "pubkey": transaction_keys[1], "blockno": blockno } unspent_fragments.append(fragment) #print("added to unspent fragments: " + fragid) if total_spendable <= 0: break ind += 1 return txn
def test_make_RIPEMD160_hash(string_input, value): """ validate that a valid RIPEMD-160 message digest format is generated """ ret = rcrypt.make_RIPEMD160_hash(string_input) assert rcrypt.validate_RIPEMD160_hash(ret) == value
def make_pkhash(): """ creates a random pkhash value """ id = rcrypt.make_uuid() return rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(id))