def parse(cls, data: Dict[Any, Any]) -> "HashedEntry": """ Creates a HashedEntry object from the provided dict. """ if not isinstance(data, dict) or not is_field_hash(data, "hash"): raise MalformedListProofError.parse_error(str(data)) key = ProofListKey.parse(data) return HashedEntry(key, Hash(bytes.fromhex(data["hash"])))
def hash_node(left: Hash, right: Hash) -> Hash: """ Convenience method to obtain a hashed value of the merkle tree node. """ data = struct.pack( "<B", Hasher.HashTag.LIST_BRANCH_NODE) + left.value + right.value return Hash.hash_data(data)
def hash_single_entry_map(path: bytes, child_hash: Hash) -> Hash: """ Hash of the map with single entry. ``` text h = sha-256( HashTag::MapBranchNode || <key> || <child_hash> ) ``` """ data = struct.pack("<B", Hasher.HashTag.MAP_BRANCH_NODE) + path + child_hash.value return Hash.hash_data(data)
def hash_map_branch(branch_node: bytes) -> Hash: """ Hash of the map branch node. ```text h = sha-256( HashTag::MapBranchNode || <left_key> || <right_key> || <left_hash> || <right_hash> ) ``` """ data = struct.pack("<B", Hasher.HashTag.MAP_BRANCH_NODE) + branch_node return Hash.hash_data(data)
def hash_map_node(root: Hash) -> Hash: """ Hash of the map object. ```text h = sha-256( HashTag::MapNode || merkle_root ) ``` """ data = struct.pack("<B", Hasher.HashTag.MAP_NODE) + root.value return Hash.hash_data(data)
def hash_list_node(length: int, merkle_root: Hash) -> Hash: """ Hash of the list object. ```text h = sha-256( HashTag::ListNode || len as u64 || merkle_root ) ``` """ data = struct.pack("<BQ", Hasher.HashTag.LIST_NODE, length) + merkle_root.value return Hash.hash_data(data)
def parse(cls, data: Dict[Any, Any]) -> "HashedEntry": """ Creates a HashedEntry object from the provided dict. """ if not isinstance(data, dict) or not is_field_hash(data, "hash"): err = MalformedListProofError.parse_error(str(data)) logger.warning( "Could not parse `hash` from dict, which is required for HashedEntry object creation. %s", str(err)) raise err key = ProofListKey.parse(data) return HashedEntry(key, Hash(bytes.fromhex(data["hash"])))
def parse(data: Dict[str, str]) -> "_MapProofEntry": """ Parses MapProofEntry from the provided dict. """ if not isinstance(data.get("path"), str) or not is_field_hash(data, "hash"): raise MalformedMapProofError.malformed_entry(data) path_bits = data["path"] path = ProofPath.parse(path_bits) data_hash = to_bytes(data["hash"]) if data_hash is None: raise MalformedMapProofError.malformed_entry(data) return _MapProofEntry(path, Hash(data_hash))
def test_hash(self) -> None: """Tests the Hash class.""" raw_hash = bytes([0xAB for _ in range(_HASH_BYTES_LEN)]) hash_obj = Hash(raw_hash) self.assertTrue(isinstance(hash_obj, _FixedByteArray)) self.assertEqual(hash_obj.value, raw_hash) self.assertEqual(hash_obj.hex(), raw_hash.hex()) self.assertEqual(Hash.hash_data(bytes()), Hash(crypto_hash_sha256(bytes()))) self.assertEqual(Hash.hash_data(bytes([1, 2])), Hash(crypto_hash_sha256(bytes([1, 2]))))
def child_hash(self, kind: str) -> Hash: """Returns a stored child hash for the specified kind ("left" or "right").""" return Hash(bytes(self.raw[self._hash_slice(kind)]))
def collect(entries: List[_MapProofEntry]) -> Hash: """ Computes the root hash of the Merkle Patricia tree backing the specified entries in the map view. The tree is not restored in full; instead, we add the paths to the tree in their lexicographic order (i.e., according to the `PartialOrd` implementation of `ProofPath`) and keep track of the rightmost nodes (the right contour) of the tree. It is easy to see that adding paths in the lexicographic order means that only the nodes in the right contour may be updated on each step. Further, on each step zero or more nodes are evicted from the contour, and a single new node is added to it. `entries` are assumed to be sorted by the path in the increasing order. """ def common_prefix(left: ProofPath, right: ProofPath) -> ProofPath: return left.prefix(left.common_prefix_len(right)) def hash_branch(left_child: _MapProofEntry, right_child: _MapProofEntry) -> Hash: branch = BranchNode() branch.set_child("left", left_child.path, left_child.hash) branch.set_child("right", right_child.path, right_child.hash) return branch.object_hash() def fold(contour: List[_MapProofEntry], last_prefix: ProofPath) -> Optional[ProofPath]: last_entry = contour.pop() penultimate_entry = contour.pop() contour.append(_MapProofEntry(path=last_prefix, data_hash=hash_branch(penultimate_entry, last_entry))) if len(contour) > 1: penultimate_entry = contour[len(contour) - 2] return common_prefix(penultimate_entry.path, last_prefix) return None if not entries: return Hash(Hasher.DEFAULT_HASH) if len(entries) == 1: if not entries[0].path.is_leaf(): raise MalformedMapProofError.non_terminal_node(entries[0].path) return Hasher.hash_single_entry_map(entries[0].path.as_bytes(), entries[0].hash) # There is more than 1 entry. # Contour of entries to be folded into the result hash: contour: List[_MapProofEntry] = [] # Initical contour state: first_entry, second_entry = entries[0], entries[1] last_prefix = common_prefix(first_entry.path, second_entry.path) contour = [first_entry, second_entry] # Process the rest of the entries: for entry in entries[2:]: new_prefix = common_prefix(contour[-1].path, entry.path) # Fold contour from the latest added entry to the beginning. # At each iteration take two latest entries and attempt to fold them into one new entry: while len(contour) > 1 and len(new_prefix) < len(last_prefix): prefix = fold(contour, last_prefix) if prefix is not None: last_prefix = prefix contour.append(entry) last_prefix = new_prefix # All entries are processed. Fold the contour into the final hash: while len(contour) > 1: prefix = fold(contour, last_prefix) if prefix: last_prefix = prefix return contour[0].hash
def hash_leaf(val: bytes) -> Hash: """ Convenience method to obtain a hashed value of the merkle tree leaf. """ data = struct.pack("<B", Hasher.HashTag.BLOB) + val return Hash.hash_data(data)
def hash_raw_data(data: bytes) -> Hash: """ SHA256 hash of the provided data. """ return Hash.hash_data(data)
def run() -> None: """This example creates a wallet in the Cryptocurrency service, retrieves proofs for the wallet and verifies them. For the example to work, be sure to have `exonum-cryptocurrency-advanced` service instance with name `XNM` deployed.""" client = ExonumClient(hostname="127.0.0.1", public_api_port=8080, private_api_port=8081) with client.protobuf_loader() as loader: # Load and compile proto files: loader.load_main_proto_files() loader.load_service_proto_files(RUST_RUNTIME_ID, CRYPTOCURRENCY_ARTIFACT_NAME) instance_id = get_cryptocurrency_instance_id(client) cryptocurrency_message_generator = MessageGenerator( instance_id, CRYPTOCURRENCY_ARTIFACT_NAME) alice_keypair = create_wallet(client, cryptocurrency_message_generator, "Alice") wallet_info_response = client.get_service( CRYPTOCURRENCY_INSTANCE_NAME, "v1/wallets/info?pub_key={}".format( alice_keypair.public_key.hex())) ensure_status_code(wallet_info_response) wallet_info = wallet_info_response.json() # `MapProof` to the whole Exonum state hash: proof_to_table = wallet_info["wallet_proof"]["to_table"] # Expected hash of the proof to the table is a state hash of the block: expected_to_table_hash_raw = wallet_info["block_proof"]["block"][ "state_hash"] expected_to_table_hash = Hash( bytes.fromhex(expected_to_table_hash_raw)) # Verify the proof to the table: verify_proof_to_table(proof_to_table, expected_to_table_hash) # `MapProof` to the wallet as a part of the Cryptocurrency schema: proof_to_wallet = wallet_info["wallet_proof"]["to_wallet"] # Expected hash of the proof to the wallet is the value stored in the # proof to the table: expected_to_wallet_hash_raw = wallet_info["wallet_proof"]["to_table"][ "entries"][0]["value"] expected_to_wallet_hash = Hash( bytes.fromhex(expected_to_wallet_hash_raw)) # Verify the proof to the wallet: verify_proof_to_wallet(proof_to_wallet, expected_to_wallet_hash) # `ListProof` for the transactions associtated with the wallet: proof_wallet_history = wallet_info["wallet_history"]["proof"] # Expected hash for the wallet history is the hash stored in the proof # to the wallet: expected_history_hash_raw = wallet_info["wallet_proof"]["to_wallet"][ "entries"][0]["value"]["history_hash"] expected_history_hash = Hash(bytes(expected_history_hash_raw["data"])) # Verify the proof for the wallet history: verify_wallet_history_proof(proof_wallet_history, expected_history_hash)
def _parse_hash(hex_data: str) -> Hash: return Hash(_to_bytes(hex_data))