def test_merkle_proof_simple_false(self): tree = MerkleTree() tree.add_leaf('test', True) tree.add_leaf('test2', True) left = sha256('test') right = sha256('test2')
def test_make_tree_with_add_leaf_hex(self): tree = MerkleTree() tree.add_leaf( 'a292780cc748697cb499fdcc8cb89d835609f11e502281dfe3f6690b1cc23dcb') tree.add_leaf( 'cb4990b9a8936bbc137ddeb6dcab4620897b099a450ecdc5f3e86ef4b3a7135c') tree.make_tree() self.assertEqual(tree.get_merkle_root(), self.mRoot, 'merkle root value should be correct') hashes = [] hashes.append( 'a292780cc748697cb499fdcc8cb89d835609f11e502281dfe3f6690b1cc23dcb') hashes.append( 'cb4990b9a8936bbc137ddeb6dcab4620897b099a450ecdc5f3e86ef4b3a7135c') tree = MerkleTree() tree.add_leaves(hashes) tree.make_tree() targetProof0 = tree.get_proof(0) targetProof1 = tree.get_proof(1) self.assertEqual(tree.get_merkle_root(), self.mRoot, 'merkle root value should be correct') self.assertEqual(len(targetProof0), 1, 'merkle root value should be correct') self.assertEqual(len(targetProof1), 1, 'merkle root value should be correct')
def test_proof_one_node(self): tree = MerkleTree() tree.add_leaf(self.bLeft) tree.make_tree() proof = tree.get_proof(0) self.assertEqual(proof, [], 'proof array should be correct')
def test_proof_single_true(self): tree = MerkleTree() tree.add_leaf('test', True) tree.make_tree() proof = tree.get_proof(0) target = sha256('test') self.assertTrue( tree.validate_proof(proof, target, tree.get_merkle_root()))
def test_large_tree(self): tree = MerkleTree() for i in range(10000): tree.add_leaf((str(i)), True) tree.make_tree() ans = 'e08a41fa2a658af6f552d22570da0e9511230e4c81d421ca7f206e76770045d6' self.assertEqual(tree.get_merkle_root(), ans)
def make_tree_with_add_leaf_buffers(self): tree = MerkleTree() tree.add_leaf(self.bLeft) tree.add_leaf(self.bRight) tree.make_tree() self.assertEqual(tree.get_merkle_root(), self.mRoot, 'merkle root value should be correct')
def test_proof_single_false(self): tree = MerkleTree() tree.add_leaf('test', True) tree.make_tree() proof = tree.get_proof(1) self.assertFalse( tree.validate_proof(proof, sha256('test9'), tree.get_merkle_root()))
def test_two_even_items(self): tree = MerkleTree() tree.add_leaf('test', True) tree.add_leaf(tree.hash_f('test2')) tree.make_tree() ans = 'a0f6cfae7a24aaf251208954f67cbc0d9b87fb19e07d89a6d157fcce5ca558e9' self.assertEqual(tree.get_merkle_root(), ans)
def test_tree_odd_items(self): tree = MerkleTree() tree.add_leaf('test', True) tree.add_leaf('test2', True) tree.add_leaf('test3', True) tree.make_tree() ans = 'ab56cfafe1f1a8c4d5a526a754a07513ab92266ccdc25295e5c6e468b7e8a807' self.assertEqual(tree.get_merkle_root(), ans)
def test_proof_left_node(self): tree = MerkleTree() tree.add_leaf(self.bLeft) tree.add_leaf(self.bRight) tree.make_tree() proof = tree.get_proof(0) self.assertEqual( proof[0]['right'], 'cb4990b9a8936bbc137ddeb6dcab4620897b099a450ecdc5f3e86ef4b3a7135c', 'proof array should be correct')
def test_validate_bad_proof_2_leaves(self): tree = MerkleTree() tree.add_leaf(self.bLeft) tree.add_leaf(self.bRight) tree.make_tree() proof = tree.get_proof(1) isValid = tree.validate_proof( proof, self.bRight, 'a292780cc748697cb499fdcc8cb89d835609f11e502281dfe3f6690b1cc23dcb') self.assertFalse(isValid, 'proof should be invalid')
def test_proof_right_node(self): tree = MerkleTree() tree.add_leaf(self.bLeft) tree.add_leaf(self.bRight) tree.make_tree() proof = tree.get_proof(1) self.assertEqual( proof[0]['left'], 'a292780cc748697cb499fdcc8cb89d835609f11e502281dfe3f6690b1cc23dcb', 'proof array should be correct')
def test_proof_true(self): tree = MerkleTree() tree.add_leaf('test', True) tree.add_leaf('test2', True) tree.add_leaf('test3', True) tree.make_tree() proof = tree.get_proof(0) self.assertTrue( tree.validate_proof(proof, sha256('test'), tree.get_merkle_root()))
def test_proof_false(self): tree = MerkleTree() tree.add_leaf('test1', True) tree.add_leaf('test2', True) tree.add_leaf('test3', True) tree.make_tree() proof = tree.get_proof(4) result = tree.validate_proof(proof, sha256('test'), tree.get_merkle_root()) self.assertFalse(result)
def test_proof_get_json(self): tree = MerkleTree() tree.add_leaf('test', True) tree.add_leaf('test2', True) tree.add_leaf('test3', True) tree.make_tree() json_data = tree.get_proof(0) self.assertEqual( json_data[0]["right"], '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752') self.assertEqual( json_data[1]["right"], 'fd61a03af4f77d870fc21e05e7e80678095c92d808cfb3b5c279ee04c74aca13')
def test_make_tree_with_5_leaves_individually_needing_hashing(self): tree = MerkleTree() tree.add_leaf('a', True) tree.add_leaf('b', True) tree.add_leaf('c', True) tree.add_leaf('d', True) tree.add_leaf('e', True) tree.make_tree() self.assertEqual( tree.get_merkle_root(), 'd71f8983ad4ee170f8129f1ebcdd7440be7798d8e1c80420bf11f1eced610dba', 'merkle root value should be correct')
class BatchIssuer(Issuer): def __init__(self, netcode, issuing_address, certificates_to_issue, connector, signer, batch_metadata, tx_cost_constants): Issuer.__init__(self, netcode, issuing_address, certificates_to_issue, connector, signer) self.tree = MerkleTree(hash_f=sha256) self.batch_id = batch_metadata.batch_id self.batch_metadata = batch_metadata self.tx_cost_constants = tx_cost_constants def validate_schema(self): """ Ensure certificates are valid v1.2 schema :return: """ for _, certificate in self.certificates_to_issue.items(): with open(certificate.signed_cert_file_name) as cert: cert_json = json.load(cert) validate_unsigned_v1_2(cert_json) def do_hash_certificate(self, certificate): """ Hash the JSON-LD normalized certificate :param certificate: :return: """ options = { 'algorithm': 'URDNA2015', 'format': 'application/nquads', 'documentLoader': cached_document_loader } cert_utf8 = certificate.decode('utf-8') cert_json = json.loads(cert_utf8) normalized = jsonld.normalize(cert_json, options=options) hashed = sha256(normalized) self.tree.add_leaf(hashed, False) return hashed def calculate_cost_for_certificate_batch(self): """ Per certificate, we pay 2*min_per_output (which is based on dust) + fee. Note assumes 1 input per tx. :return: """ num_inputs = 1 # output per recipient num_outputs = len(self.certificates_to_issue) # plus revocation outputs num_outputs += sum(1 for c in self.certificates_to_issue.values() if c.revocation_key) # plus global revocation, change output, and OP_RETURN num_outputs += 3 self.total = tx_utils.calculate_tx_total(self.tx_cost_constants, num_inputs, num_outputs) return self.total def persist_tx(self, sent_tx_file_name, tx_id): Issuer.persist_tx(self, sent_tx_file_name, tx_id) # note that certificates are stored in an ordered dictionary, so we will iterate in the same order index = 0 for uid, metadata in self.certificates_to_issue.items(): receipt = self.tree.make_receipt(index, tx_id) with open(metadata.receipt_file_name, 'w') as out_file: out_file.write(json.dumps(receipt)) with open(metadata.signed_cert_file_name, 'r') as in_file: signed_cert = json.load(in_file) blockchain_cert = { '@context': 'https://w3id.org/blockcerts/v1', 'type': 'BlockchainCertificate', 'document': signed_cert, 'receipt': receipt } with open(metadata.blockchain_cert_file_name, 'w') as out_file: out_file.write(json.dumps(blockchain_cert)) index += 1 def create_transactions(self, revocation_address): """ Create the batch Bitcoin transaction :param revocation_address: :return: """ self.tree.make_tree() spendables = self.connector.get_unspent_outputs(self.issuing_address) if not spendables: error_message = 'No money to spend at address {}'.format( self.issuing_address) logging.error(error_message) raise InsufficientFundsError(error_message) last_input = spendables[-1] op_return_value = unhexlify(self.tree.get_merkle_root()) tx_outs = self.build_recipient_tx_outs() tx_outs.append( tx_utils.create_transaction_output( revocation_address, self.tx_cost_constants.get_minimum_output_coin())) transaction = tx_utils.create_trx(op_return_value, self.total, self.issuing_address, tx_outs, last_input) transaction_data = TransactionData( uid=self.batch_id, tx=transaction, tx_input=last_input, op_return_value=hexlify(op_return_value), batch_metadata=self.batch_metadata) return [transaction_data] def build_recipient_tx_outs(self): """ Creates 2 transaction outputs for each recipient: one to their public key and the other to their specific revocation key. :return: """ tx_outs = [] for _, certificate in self.certificates_to_issue.items(): recipient_outs = [ tx_utils.create_transaction_output( certificate.public_key, self.tx_cost_constants.get_minimum_output_coin()) ] if certificate.revocation_key: recipient_outs.append( tx_utils.create_transaction_output( certificate.revocation_key, self.tx_cost_constants.get_minimum_output_coin())) tx_outs = tx_outs + recipient_outs return tx_outs
class BatchIssuer(Issuer): def __init__(self, config, certificates_to_issue): Issuer.__init__(self, config, certificates_to_issue) self.batch_id = '%024x' % random.randrange(16**24) self.tree = MerkleTree(hash_f=sha256) def validate_schema(self): """ Ensure certificates are valid v1.2 schema :return: """ for _, certificate in self.certificates_to_issue.items(): with open(certificate.signed_certificate_file_name) as cert: cert_json = json.load(cert) schema_validator.validate_unsigned_v1_2(cert_json) def do_hash_certificate(self, certificate): """ Hash the JSON-LD normalized certificate :param certificate: :return: """ cert_utf8 = certificate.decode('utf-8') cert_json = json.loads(cert_utf8) normalized = jsonld.normalize(cert_json, { 'algorithm': 'URDNA2015', 'format': 'application/nquads' }) hashed = sha256(normalized) self.tree.add_leaf(hashed, False) return hashed def get_cost_for_certificate_batch(self, allow_transfer): """ Per certificate, we pay 2*min_per_output (which is based on dust) + fee. Note assumes 1 input per tx. We may also need to pay additional fees for splitting into temp addresses :param allow_transfer: :return: """ num_certificates = len(self.certificates_to_issue) num_outputs = Issuer.get_num_outputs(num_certificates) return Issuer.get_cost_for_certificate_batch(num_outputs, allow_transfer) def finish_tx(self, sent_tx_file_name, tx_id): Issuer.finish_tx(self, sent_tx_file_name, tx_id) # note that certificates are stored in an ordered dictionary, so we will iterate in the same order index = 0 for uid, _ in self.certificates_to_issue.items(): receipt = self.tree.make_receipt(index, tx_id) receipt_file_name = convert_file_name( self.config.receipts_file_pattern, uid) with open(receipt_file_name, 'w') as out_file: out_file.write(json.dumps(receipt)) signed_cert_file_name = convert_file_name( self.config.signed_certs_file_pattern, uid) with open(signed_cert_file_name, 'r') as in_file: signed_cert = json.load(in_file) blockchain_cert = { '@context': 'https://w3id.org/blockcerts/v1', 'type': 'BlockchainCertificate', 'document': signed_cert, 'receipt': receipt } blockchain_cert_file_name = convert_file_name( self.config.blockchain_certificates_file_pattern, uid) with open(blockchain_cert_file_name, 'w') as out_file: out_file.write(json.dumps(blockchain_cert)) index += 1 def create_transactions(self, revocation_address, issuing_transaction_cost): """ Create the batch Bitcoin transaction :param revocation_address: :param issuing_transaction_cost: :return: """ self.tree.make_tree() spendables = get_unspent_outputs(self.issuing_address) if not spendables: error_message = 'No money to spend at address {}'.format( self.issuing_address) logging.error(error_message) raise InsufficientFundsError(error_message) last_input = spendables[-1] op_return_value = unhexlify(self.tree.get_merkle_root()) tx_outs = self.build_recipient_tx_outs() tx_outs.append( trx_utils.create_transaction_output( revocation_address, issuing_transaction_cost.min_per_output)) transaction = trx_utils.create_trx(op_return_value, issuing_transaction_cost, self.issuing_address, tx_outs, last_input) unsigned_tx_file_name = convert_file_name( self.config.unsigned_txs_file_pattern, self.batch_id) unsent_tx_file_name = convert_file_name( self.config.signed_txs_file_pattern, self.batch_id) sent_tx_file_name = convert_file_name( self.config.sent_txs_file_pattern, self.batch_id) transaction_data = TransactionData( uid=self.batch_id, tx=transaction, tx_input=last_input, op_return_value=hexlify(op_return_value), unsigned_tx_file_name=unsigned_tx_file_name, signed_tx_file_name=unsent_tx_file_name, sent_tx_file_name=sent_tx_file_name) return [transaction_data] def build_recipient_tx_outs(self): """ Creates 2 transaction outputs for each recipient: one to their public key and the other to their specific revocation key. :return: """ tx_outs = [] for _, certificate in self.certificates_to_issue.items(): tx_outs = tx_outs + trx_utils.create_recipient_outputs( certificate.public_key, certificate.revocation_key) return tx_outs
class V1_2_Issuer(Issuer): def __init__(self, config, certificates_to_issue): Issuer.__init__(self, config, certificates_to_issue) self.batch_id = '%024x' % random.randrange(16**24) self.tree = MerkleTree(hash_f=sha256) def validate_schema(self): # ensure certificates are valid v1.2 schema for uid, certificate in self.certificates_to_issue.items(): with open(certificate.signed_certificate_file_name) as cert: cert_json = json.load(cert) schema_validator.validate_unsigned_v1_2(cert_json) # TODO: duplicated with cert-verifier def do_hash_certificate(self, certificate): cert_utf8 = certificate.decode('utf-8') cert_json = json.loads(cert_utf8) normalized = jsonld.normalize(cert_json, { 'algorithm': 'URDNA2015', 'format': 'application/nquads' }) hashed = sha256(normalized) self.tree.add_leaf(hashed, False) return hashed def get_cost_for_certificate_batch(self, dust_threshold, recommended_fee_per_transaction, satoshi_per_byte, allow_transfer): ''' Per certificate, we pay 2*min_per_output (which is based on dust) + fee. Note assumes 1 input per tx. We may also need to pay additional fees for splitting into temp addresses ''' num_certificates = len(self.certificates_to_issue) num_outputs = Issuer.get_num_outputs(num_certificates) return Issuer.get_cost_for_certificate_batch( dust_threshold, recommended_fee_per_transaction, satoshi_per_byte, num_outputs, allow_transfer, 1, 1) def finish_tx(self, sent_tx_file_name, txid): Issuer.finish_tx(self, sent_tx_file_name, txid) # note that certificates are stored in an ordered dictionary, so we will iterate in the same order index = 0 for uid, certificate in self.certificates_to_issue.items(): receipt = self.tree.make_receipt(index, txid) receipt_file_name = convert_file_name( self.config.receipts_file_pattern, uid) with open(receipt_file_name, 'w') as out_file: out_file.write(json.dumps(receipt)) signed_cert_file_name = convert_file_name( self.config.signed_certs_file_pattern, uid) with open(signed_cert_file_name, 'r') as in_file: signed_cert = json.load(in_file) blockchain_cert = { '@context': 'https://w3id.org/blockcerts/v1', 'type': 'BlockchainCertificate', 'document': signed_cert, 'receipt': receipt } blockchain_cert_file_name = convert_file_name( self.config.blockchain_certificates_file_pattern, uid) with open(blockchain_cert_file_name, 'w') as out_file: out_file.write(json.dumps(blockchain_cert)) index += 1 def create_transactions(self, wallet, revocation_address, issuing_transaction_cost, split_input_trxs): # finish tree self.tree.make_tree() op_return_value = unhexlify(self.tree.get_merkle_root()) unspent_outputs = wallet.get_unspent_outputs(self.issuing_address) last_output = unspent_outputs[-1] txouts = self.build_txouts(issuing_transaction_cost) txouts = txouts + [ trx_utils.create_transaction_output( revocation_address, issuing_transaction_cost.min_per_output) ] tx = trx_utils.create_trx(op_return_value, issuing_transaction_cost, self.issuing_address, txouts, last_output) unsigned_tx_file_name = convert_file_name( self.config.unsigned_txs_file_pattern, self.batch_id) unsent_tx_file_name = convert_file_name( self.config.signed_txs_file_pattern, self.batch_id) sent_tx_file_name = convert_file_name( self.config.sent_txs_file_pattern, self.batch_id) td = TransactionData(uid=self.batch_id, tx=tx, tx_input=last_output, op_return_value=hexlify(op_return_value), unsigned_tx_file_name=unsigned_tx_file_name, signed_tx_file_name=unsent_tx_file_name, sent_tx_file_name=sent_tx_file_name) return [td] def build_txouts(self, issuing_transaction_cost): txouts = [] for uid, certificate in self.certificates_to_issue.items(): txouts = txouts + trx_utils.create_recipient_outputs( issuing_transaction_cost.min_per_output, certificate.public_key, certificate.revocation_key) return txouts
def make_tree_with_add_leaf_bad_hex(self): tree = MerkleTree() self.assertRaises(Exception, tree.add_leaf('nothexandnothashed'))