def apply_stake_actions(self, validators, actions): for action in actions: if isinstance(action, PenaltyTransaction): for conflict in action.conflicts: culprit = self.get_block_validator(conflict) self.release_stake(validators, Keys.to_bytes(culprit.public_key)) elif isinstance(action, PenaltyGossipTransaction): culprit = self.get_conflict_gossip_sender(action) self.release_stake(validators, Keys.to_bytes(culprit)) elif isinstance(action, StakeHoldTransaction): self.hold_stake(validators, action.pubkey, action.amount) elif isinstance(action, StakeReleaseTransaction): self.release_stake(validators, action.pubkey) return validators
def filter_out_skipped_public_keys(private_keys, public_keys): filtered_private_keys = [] for private_key in private_keys: if private_key == None: filtered_private_keys.append(None) else: expected_public = Private.publickey( Keys.from_bytes(private_key)) if Keys.to_bytes(expected_public) in public_keys.values(): filtered_private_keys.append(private_key) return filtered_private_keys
def extract_shared_random(self, block_hash): if block_hash == self.dag.genesis_block().get_hash(): return 0 private_keys = self.get_private_keys_for_epoch(block_hash) public_keys = self.get_public_keys_for_epoch(block_hash) published_private_keys = self.filter_out_skipped_public_keys( private_keys, public_keys) random_pieces_list = self.get_random_splits_for_epoch(block_hash) # self.log("pubkeys") # for _, public_key in public_keys.items(): # self.log(Keys.to_visual_string(public_key)) # self.log("privkeys converted") private_key_count = 0 # amount of sent keys matching_keys_count = 0 # amount of keys which have matching pubkeys for key in published_private_keys: if not key: # self.log("None") continue pubkey = Private.publickey(key) # self.log(Keys.to_visual_string(pubkey)) private_key_count += 1 if Keys.to_bytes(pubkey) in public_keys.values(): matching_keys_count += 1 pubkey_count = len(public_keys) self.log("pubkey count", pubkey_count, "privkey count", private_key_count, "of them matching", matching_keys_count) half_of_pubkeys = int(pubkey_count / 2) + 1 half_of_privkeys = int(private_key_count / 2) + 1 # TODO we should have a special handling for when not enough keys was sent for each round assert pubkey_count > 1, "Not enough public keys to decrypt random" assert private_key_count > 1, "Not enough private keys to decrypt random" assert pubkey_count >= half_of_privkeys, "Not enough public keys to decrypt random" assert private_key_count >= half_of_pubkeys, "Not enough private keys to decrypt random" assert matching_keys_count >= half_of_pubkeys, "Not enough matching keys in epoch" assert matching_keys_count >= half_of_privkeys, "Not enough matching keys in epoch" ordered_private_keys_count = len( private_keys) # total amount of both sent and unsent keys randoms_list = [] for random_pieces in random_pieces_list: assert ordered_private_keys_count >= len( random_pieces ), "Amount of splits must match amount of public keys" random = decode_random( random_pieces, Keys.list_from_bytes(published_private_keys)) randoms_list.append(random) seed = sum_random(randoms_list) return seed
def test_private_keys_extraction(self): dag = Dag(0) epoch = Epoch(dag) node_private = Private.generate() prev_hash = dag.genesis_block().get_hash() round_start, round_end = Epoch.get_round_bounds(1, Round.PRIVATE) for i in range(1, round_start): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() generated_private_keys = [] for i in range(round_start, round_end): # intentionally skip last block of round generated_private = Private.generate() generated_private_keys.append(Keys.to_bytes(generated_private)) private_key_tx = PrivateKeyTransaction() private_key_tx.key = Keys.to_bytes(generated_private) block = Block() block.system_txs = [private_key_tx] block.prev_hashes = dag.get_top_blocks_hashes() block.timestamp = i * BLOCK_TIME signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() ChainGenerator.fill_with_dummies(dag, prev_hash, Epoch.get_round_range(1, Round.FINAL)) epoch_hash = dag.blocks_by_number[ROUND_DURATION * 6 + 1][0].get_hash() extracted_privates = epoch.get_private_keys_for_epoch(epoch_hash) for i in range(0, ROUND_DURATION - 1): self.assertEqual(extracted_privates[i], generated_private_keys[i])
def test_pack_parse_reveal_transaction(self): for _ in range(10): dummy_private = Private.generate() original = RevealRandomTransaction() original.commit_hash = sha256(b"previous_transaction").digest() original.key = Keys.to_bytes(dummy_private) raw = original.pack() restored = RevealRandomTransaction() restored.parse(raw) self.assertEqual(TransactionParser.pack(original), TransactionParser.pack(restored)) self.assertEqual(original.get_hash(), restored.get_hash())
def create_dummy_commit_reveal(random_bytes, epoch_hash): node_private = Private.generate() private = Private.generate() encoded = Private.encrypt(random_bytes, private) commit = CommitRandomTransaction() commit.rand = encoded commit.pubkey_index = 10 commit.signature = Private.sign(commit.get_signing_hash(epoch_hash), node_private) reveal = RevealRandomTransaction() reveal.commit_hash = commit.get_hash() reveal.key = Keys.to_bytes(private) return commit, reveal
def test_stake_release_by_genesis_validator(self): # base initialization dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash for i in range(1, 9): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() # get one of validators genesis_validator = initial_validators[9] # create stake release transaction for new stakeholder tx_release = StakeReleaseTransaction() tx_release.pubkey = Keys.to_bytes(genesis_validator.public_key) tx_release.signature = Private.sign(tx_release.get_hash(), node_private) # append signed stake release transaction block.system_txs.append(tx_release) # sign block by one of validators signed_block = BlockFactory.sign_block(block, node_private) # add signed block to DAG dag.add_signed_block(19, signed_block) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertNotIn(genesis_validator.public_key, pub_keys)
def form_split_random_transaction(self, top_hash, epoch_hash): ordered_senders = self.permissions.get_ordered_randomizers_pubkeys_for_round( epoch_hash, Round.PUBLIC) published_pubkeys = self.epoch.get_public_keys_for_epoch(top_hash) self.logger.info("Ordered pubkeys for secret sharing:") sorted_published_pubkeys = [] for sender in ordered_senders: raw_pubkey = Keys.to_bytes(sender.public_key) raw_pubkey_index = self.permissions.get_signer_index_from_public_key( raw_pubkey, epoch_hash) if raw_pubkey_index in published_pubkeys: generated_pubkey = published_pubkeys[raw_pubkey_index] sorted_published_pubkeys.append( Keys.from_bytes(generated_pubkey)) self.logger.info(Keys.to_visual_string(generated_pubkey)) else: sorted_published_pubkeys.append(None) self.logger.info("None") tx = self.form_secret_sharing_transaction(sorted_published_pubkeys, epoch_hash) return tx
def validate_private_transactions_in_block(self, block, current_round): private_key_transactions = [] for tx in block.system_txs: if isinstance(tx, PrivateKeyTransaction): private_key_transactions.append(tx) private_key_transactions_count_in_block = len(private_key_transactions) if current_round != Round.PRIVATE: if private_key_transactions_count_in_block > 0: raise AcceptionException( "PrivateKeyTransaction was found in round " + current_round.name) return if private_key_transactions_count_in_block == 0: raise AcceptionException( "Block has no PrivateKeyTransaction in private round!") elif private_key_transactions_count_in_block > 1: raise AcceptionException( "Block has more than one PrivateKeyTransaction!") return # this check is too slow and I'm not sure if it's needed at all private_key = private_key_transactions[0].key expected_public = Private.publickey(private_key) epoch_hashes = self.epoch.get_epoch_hashes() for top, _epoch_hash in epoch_hashes.items(): public_keys = self.epoch.get_public_keys_for_epoch(top) if not Keys.to_bytes(expected_public) in public_keys.values(): raise AcceptionException( "No corresponding public key was found for private key in PrivateKeyTransaction!" )
def test_secret_sharing_rounds(self): dag = Dag(0) epoch = Epoch(dag) dummy_private = Private.generate() signers = [] for i in range(0, ROUND_DURATION + 1): signers.append(Private.generate()) private_keys = [] block_number = 1 genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash signer_index = 0 for i in Epoch.get_round_range(1, Round.PUBLIC): private = Private.generate() private_keys.append(private) signer = signers[signer_index] pubkey_tx = PublicKeyTransaction() pubkey_tx.generated_pubkey = Private.publickey(private) pubkey_tx.pubkey_index = signer_index pubkey_tx.signature = Private.sign( pubkey_tx.get_signing_hash(genesis_hash), signer) block = Block() block.timestamp = i * BLOCK_TIME block.prev_hashes = [prev_hash] block.system_txs = [pubkey_tx] signed_block = BlockFactory.sign_block(block, signer) dag.add_signed_block(i, signed_block) signer_index += 1 prev_hash = block.get_hash() prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.COMMIT)) public_keys = [] for private in private_keys: public_keys.append(Private.publickey(private)) randoms_list = [] expected_random_pieces = [] for i in Epoch.get_round_range(1, Round.SECRETSHARE): random_bytes = os.urandom(32) random_value = int.from_bytes(random_bytes, byteorder='big') split_random_tx = SplitRandomTransaction() splits = split_secret(random_bytes, 2, 3) encoded_splits = encode_splits(splits, public_keys) split_random_tx.pieces = encoded_splits split_random_tx.pubkey_index = 0 expected_random_pieces.append(split_random_tx.pieces) split_random_tx.signature = Private.sign(pubkey_tx.get_hash(), dummy_private) block = Block() block.timestamp = i * BLOCK_TIME block.prev_hashes = [prev_hash] block.system_txs = [split_random_tx] signed_block = BlockFactory.sign_block(block, dummy_private) dag.add_signed_block(i, signed_block) randoms_list.append(random_value) prev_hash = block.get_hash() expected_seed = sum_random(randoms_list) prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.REVEAL)) signer_index = 0 private_key_index = 0 raw_private_keys = [] for i in Epoch.get_round_range(1, Round.PRIVATE): private_key_tx = PrivateKeyTransaction() private_key_tx.key = Keys.to_bytes(private_keys[private_key_index]) raw_private_keys.append(private_key_tx.key) signer = signers[signer_index] block = Block() block.system_txs = [private_key_tx] block.prev_hashes = [prev_hash] block.timestamp = block_number * BLOCK_TIME signed_block = BlockFactory.sign_block(block, signer) dag.add_signed_block(i, signed_block) signer_index += 1 private_key_index += 1 prev_hash = block.get_hash() prev_hash = ChainGenerator.fill_with_dummies( dag, prev_hash, Epoch.get_round_range(1, Round.FINAL)) top_block_hash = dag.get_top_blocks_hashes()[0] random_splits = epoch.get_random_splits_for_epoch(top_block_hash) self.assertEqual(expected_random_pieces, random_splits) restored_randoms = [] for i in range(0, len(random_splits)): random = decode_random(random_splits[i], Keys.list_from_bytes(raw_private_keys)) restored_randoms.append(random) self.assertEqual(randoms_list, restored_randoms) seed = epoch.extract_shared_random(top_block_hash) self.assertEqual(expected_seed, seed)
def test_release_stake(self): # base initialization dag = Dag(0) epoch = Epoch(dag) permissions = Permissions(epoch) node_private = Private.generate() initial_validators = Validators.read_genesis_validators_from_file() genesis_hash = dag.genesis_block().get_hash() prev_hash = genesis_hash for i in range(1, 9): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * 9) # create new node for stake hold new_node_private = Private.generate() new_node_public = Private.publickey(new_node_private) # create transaction for stake hold for new node tx_hold = StakeHoldTransaction() tx_hold.amount = 2000 tx_hold.pubkey = Keys.to_bytes(new_node_public) tx_hold.signature = Private.sign(tx_hold.get_hash(), new_node_private) # append signed stake hold transaction block.system_txs.append(tx_hold) # sign block by one of validators signed_block = BlockFactory.sign_block(block, node_private) # add signed block to DAG dag.add_signed_block(9, signed_block) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertIn(new_node_public, pub_keys) # add blocks for new epoch for i in range(10, 18): block = BlockFactory.create_block_with_timestamp([prev_hash], BLOCK_TIME * i) signed_block = BlockFactory.sign_block(block, node_private) dag.add_signed_block(i, signed_block) prev_hash = block.get_hash() # create stake release transaction for new stakeholder tx_release = StakeReleaseTransaction() tx_release.pubkey = Keys.to_bytes(new_node_public) tx_release.signature = Private.sign(tx_hold.get_hash(), new_node_private) # append signed stake release transaction block.system_txs.append(tx_release) # sign block by one of validators signed_block = BlockFactory.sign_block(block, node_private) # add signed block to DAG dag.add_signed_block(19, signed_block) # verify that new stake holder now is NOT in validators list (after stake release transaction signed by holder) resulting_validators = permissions.get_validators(block.get_hash()) pub_keys = [] for validator in resulting_validators: pub_keys.append(validator.public_key) self.assertNotIn(new_node_public, pub_keys)
def create_private_key_transaction(epoch_private_key): tx = PrivateKeyTransaction() tx.key = Keys.to_bytes(epoch_private_key) return tx
def create_reveal_random_transaction(commit_hash, private): tx = RevealRandomTransaction() tx.commit_hash = commit_hash tx.key = Keys.to_bytes(private) return tx