forked from exonum/exonum-python-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproofs.py
151 lines (117 loc) · 5.99 KB
/
proofs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""Example of Obtaining and Verifying Proofs."""
from typing import Dict, Any
from exonum_client import ExonumClient, MessageGenerator, ModuleManager
from exonum_client.crypto import Hash
from exonum_client.proofs import (
ListProof,
MapProof,
MalformedMapProofError,
build_encoder_function,
ListProofVerificationError,
MalformedListProofError,
)
from examples.deploy import (
RUST_RUNTIME_ID,
CRYPTOCURRENCY_ARTIFACT_NAME,
CRYPTOCURRENCY_ARTIFACT_VERSION,
CRYPTOCURRENCY_INSTANCE_NAME,
)
from examples.transactions import create_wallet, get_cryptocurrency_instance_id, ensure_status_code
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, CRYPTOCURRENCY_ARTIFACT_VERSION)
instance_id = get_cryptocurrency_instance_id(client)
cryptocurrency_message_generator = MessageGenerator(
instance_id, CRYPTOCURRENCY_ARTIFACT_NAME, CRYPTOCURRENCY_ARTIFACT_VERSION
)
alice_keypair = create_wallet(client, cryptocurrency_message_generator, "Alice")
service_public_api = client.service_public_api(CRYPTOCURRENCY_INSTANCE_NAME)
wallet_info_response = service_public_api.get_service(
"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 verify_proof_to_table(proof: Dict[Any, Any], expected_hash: Hash) -> None:
"""Verifies MapProof to table."""
# Keys in the proof to the table are encoded as a byte sequence (tag,
# group_id, index_id):
def key_encoder(data: str) -> bytes:
return bytes(data, "utf-8")
# Values in the proof to the table are encoded as a byte sequence parsed
# from a hexadecimal string:
def value_encoder(data: str) -> bytes:
return bytes.fromhex(data)
try:
parsed_proof = MapProof.parse(proof, key_encoder, value_encoder)
result = parsed_proof.check()
if result.root_hash() == expected_hash:
print("MapProof to table verified successfully")
else:
print("MapProof to table verification failed")
except MalformedMapProofError:
print("Received malformed proof to the table")
def verify_proof_to_wallet(proof: Dict[Any, Any], expected_hash: Hash) -> None:
"""Verifies MapProof to table."""
# Keys in the proof to the wallet are encoded as a byte sequence parsed from
# a hexadecimal string:
def key_encoder(data: str) -> bytes:
return bytes.fromhex(data)
# Values in the proof to the wallet are encoded as a Protobuf binary
# representation of the `Wallet` structure:
cryptocurrency_module = ModuleManager.import_service_module(
CRYPTOCURRENCY_ARTIFACT_NAME, CRYPTOCURRENCY_ARTIFACT_VERSION, "service"
)
value_encoder = build_encoder_function(cryptocurrency_module.Wallet)
try:
parsed_proof = MapProof.parse(proof, key_encoder, value_encoder, raw=True)
result = parsed_proof.check()
if result.root_hash() == expected_hash:
print("MapProof to wallet verified successfully")
else:
print("MapProof to wallet verification failed")
except MalformedMapProofError:
print("Received malformed proof to the wallet")
def verify_wallet_history_proof(proof: Dict[Any, Any], expected_hash: Hash) -> None:
"""Verifies ListProof for the wallet history."""
# To convert a value to bytes we can use `bytes.fromhex` since values are
# hexadecimal strings:
try:
parsed_proof = ListProof.parse(proof, value_to_bytes=bytes.fromhex)
parsed_proof.validate(expected_hash)
print("ListProof for the wallet history verified successfully")
except ListProofVerificationError:
print("ListProof for the wallet history verification failed")
except MalformedListProofError:
print("Received malformed proof for the wallet history")
if __name__ == "__main__":
run()