Client
Function Description
The Client provides an interface for users to send requests and process Node data received from the Hub.
The Client communicates with the Node via the gRPC protocol, sending input data and receiving proofs.
Main Process
Establishing HTTPS Connection
Request Node List: Send a request to the Hub to obtain the Node list.
Receive Node Data: Receive the Node list from the Hub.
Request Node List (with Payment):
First, establish a new order request with the Hub and obtain the order number.
Afterward, call the corresponding contract function for payment.
Once the payment is completed, use the order number to request the Node list from the Hub.
Receive Node Data: Receive the Node list from the Hub.
Code Example
Users first establish an HTTPS connection with the Hub, which returns a JSON object containing response codes and node addresses array.
A response code of
0
indicates a successful request.
Example Request:
curl -X GET "https://prove-api.zerobase.pro/api/v1/hub/node"
Example return:
{
"code": 0,
"msg": "Successfully",
"results": [{
"grpc_info": {
"address": "",
"timestamp": 0
},
"http_info": {
"address": "",
"timestamp": 0
},
"poh": ""
}]
}
Define gRPC Protocol
Define the gRPC service and message formats using .proto
files.
syntax = "proto3";
package prove_service;
message ProveBaseRequest {
string prover_id = 1; // Unique identifier for the prover
string circuit_template_id = 2; // Circuit template identifier
string input_data = 3; // Input data used to generate the proof
bool is_encrypted = 4; // True if the input data is encrypted
string auth_token = 5; // Authentication token
}
message StatusResponse {
int32 code = 1; // Status code
string msg = 2; // Additional status information
}
message ProveRequest {
ProveBaseRequest base_request = 1; // Base request for all proof requests
int32 method = 2; // Optional: Method (default value is zkLogin)
string oauth_provider = 3; // Optional: OAuth provider (default value is google)
}
message ProveWithWitnessRequest {
ProveBaseRequest base_request = 1; // Base request for all proof requests
int32 method = 2; // Optional: Method (default value is zkLogin)
string oauth_provider = 3; // Optional: OAuth provider (default value is google)
}
message ProveNosha256Request {
ProveBaseRequest base_request = 1; // Base request for all proof requests
int32 length = 2; // Length of the input data
int32 method = 3; // Optional: Method (default value is zkLogin)
string oauth_provider = 4; // Optional: OAuth provider (default value is google)
}
message ProveNosha256WithWitnessRequest {
ProveBaseRequest base_request = 1; // Base request for all proof requests
int32 length = 2; // Length of the input data
int32 method = 3; // Optional: Method (default value is zkLogin)
string oauth_provider = 4; // Optional: OAuth provider (default value is google)
}
message ProveNosha256OffchainRequest {
ProveBaseRequest base_request = 1; // Base request for all proof requests
int32 length = 2; // Length of the input data
int32 method = 3; // Optional: Method (default value is zkLogin)
string oauth_provider = 4; // Optional: OAuth provider (default value is google)
}
message GetPublicKeyResponse {
StatusResponse base_response = 1; // Status code and message
string public_key = 2; // Retrieved public key string
}
message ProveResponse {
StatusResponse base_response = 1; // Status code and message
string proof_data = 2; // Generated proof data
}
message ProveWithWitnessResponse {
StatusResponse base_response = 1; // Status code and message
string proof_data = 2; // Generated proof data
string witness_data = 3; // Additional witness data related to the proof
}
message ProveNosha256Response {
StatusResponse base_response = 1; // Status code and message
string proof_data = 2; // Generated proof data
}
message ProveNosha256WithWitnessResponse {
StatusResponse base_response = 1; // Status code and message
string proof_data = 2; // Generated proof data
string witness_data = 3; // Additional witness data related to the proof
}
message ProveNosha256OffchainResponse {
StatusResponse base_response = 1; // Status code and message
bytes proof_data = 2; // Generated proof data
string witness_data = 3; // Additional witness data related to the proof
}
message Empty {
// An empty message used for requests or responses that don't require additional parameters
}
service ProveService {
rpc Prove(ProveRequest) returns (ProveResponse); // Generates a proof using the provided parameters
rpc ProveWithWitness(ProveWithWitnessRequest) returns (ProveWithWitnessResponse); // Generates a proof with witness
rpc ProveNosha256(ProveNosha256Request) returns (ProveNosha256Response); // Processes the input without SHA256 and returns a proof
rpc ProveNosha256WithWitness(ProveNosha256WithWitnessRequest) returns (ProveNosha256WithWitnessResponse); // Processes the input without SHA256 and returns a proof and witness
rpc ProveNosha256Offchain(ProveNosha256OffchainRequest) returns (ProveNosha256OffchainResponse); // Processes the input without SHA256 and returns a proof and witness for offchain verify
rpc GetPublicKey(Empty) returns (GetPublicKeyResponse); // Retrieves the public key for encrypting input data
rpc Ping(Empty) returns (Empty); // Ping for health check or to keep the connection alive
}
Install gRPC Tools
Install grpcio-tools
using pip to generate gRPC code:
pip install grpcio-tools
Generate gRPC Code
Use protoc
to generate Python service and message handling code.
1.Once the installation is complete, navigate to the directory where the .proto
file is stored.
2.Open a command-line tool and ensure that the current working directory is the one where the .proto
file is located.
3.Execute the following command to generate Python code: python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. Prove.proto
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. Prove.proto
-I.
— Specifies the directory whereprotoc
searches for imported.proto
files (in this case, the current directory).--python_out=.
— Specifies the output directory for the generated Python class files.--grpc_python_out=.
— Specifies the output directory for the generated gRPC service code.prove.proto
— The name of your.proto
file.
After executing this command, two files will be generated in the same directory:
prove_pb2.py
:Contains Python classes for the messages and enums defined in your .proto file.prove_pb2_grpc.py
:Contains server and client classes that can be used to implement and call gRPC methods.
Implement Python gRPC Client Code
Implement gRPC communication with the Node.
Send requests and handle the returned data.
Code Example:
import grpc
import time
from modules.prove_service import prove_service_pb2, prove_service_pb2_grpc
from utils.constant import GRPC_STATUS_ERROR, GRPC_STATUS_SUCCESSFULLY
class ProveServiceClient:
def __init__(self, address: str) -> None:
self.address = address
async def _make_request(self, method_name: str, request_message):
"""
Helper method to handle gRPC requests.
"""
try:
async with grpc.aio.insecure_channel(self.address) as channel:
stub = prove_service_pb2_grpc.ProveServiceStub(channel)
method = getattr(stub, method_name)
response = await method(request_message)
return response
except grpc.aio.AioRpcError as e:
return GRPC_STATUS_ERROR, str(e), None
async def prove(self, prover: str, temp: str, input_data: str, is_encrypted: bool, token: str):
request = prove_service_pb2.ProveRequest(
prover=prover,
temp=temp,
input=input_data,
is_encrypted=is_encrypted,
token=token
)
response = await self._make_request('prove', request)
return response.code, response.msg, response.proof
async def prove_nosha256(self, prover: str, temp: str, input_data: str, is_encrypted: bool, token: str, length: int):
request = prove_service_pb2.ProveNosha256Request(
prover=prover,
temp=temp,
input=input_data,
is_encrypted=is_encrypted,
token=token,
length=length
)
response = await self._make_request('prove_nosha256', request)
return response.code, response.msg, response.proof
async def prove_to_cairo(self, prover: str, temp: str, input_data: str, is_encrypted: bool, token: str):
request = prove_service_pb2.ProveToCairoRequest(
prover=prover,
temp=temp,
input=input_data,
is_encrypted=is_encrypted,
token=token
)
response = await self._make_request('prove2cairo', request)
return response.code, response.msg, response.proof, response.witness
async def prove_nosha256_offchain(self, prover: str, temp: str, input_data: str, is_encrypted: bool, token: str, length: int):
request = prove_service_pb2.ProveNosha256OffchainRequest(
prover=prover,
temp=temp,
input=input_data,
is_encrypted=is_encrypted,
token=token,
length=length
)
response = await self._make_request('prove_nosha256_offchain', request)
return response.code, response.msg, response.proof, response.witness
async def get_public_key(self):
response = await self._make_request('get_public_key', prove_service_pb2.Empty())
return response.code, response.public_key
async def ping(self):
try:
start_time = time.time()
async with grpc.aio.insecure_channel(self.address) as channel:
stub = prove_service_pb2_grpc.ProveServiceStub(channel)
await stub.ping(prove_service_pb2.Empty())
end_time = time.time()
spent_time = end_time - start_time
return GRPC_STATUS_SUCCESSFULLY, spent_time
except grpc.aio.AioRpcError as e:
return GRPC_STATUS_ERROR, str(e)
Task Preparation and Execution
Prepare concurrent tasks to send input data to each Node.
Optionally, apply encryption to the data before sending.
User sequentially use gRPC to send inputs to the corresponding nodes:
async def prepare_prove_task(self, node, prover, temp, input_data, is_encrypt, token):
prove_node = ProveServiceClient(node)
try:
if is_encrypt:
code, public_key = await prove_node.get_public_key()
if code == GRPC_STATUS_ERROR:
return code, public_key, node
encrytor = RSAEncryption(public_key=public_key)
input_data = encrytor.encrypt(input_data)
code, msg, proof = await prove_node.prove(prover=prover, temp=temp, input=input_data, is_encrypted=is_encrypt, token=token)
return code, msg, proof, node
except Exception as e:
return GRPC_STATUS_ERROR, str(e), node
Implement and Invoke Decision Tree
Collect verification results from each Node.
Use a decision tree to determine which verification results are valid.
Users store successful proofs in the decision tree, which determines available proofs and returns the correct result:
import time
from typing import List, Optional, Union
class Proof:
def __init__(self) -> None:
self.proof_list: List[dict] = []
def add(self, proof: str) -> None:
"""
Adds a proof to the list along with a timestamp.
:param proof: The proof string to add.
"""
self.proof_list.append({"proof": proof, "timestamp": time.time()})
def get_proof(self) -> Union[str, bool]:
"""
Retrieves the most frequent proof within 1 second of the earliest proof timestamp.
:return: The most frequent proof if found, otherwise False.
"""
if not self.proof_list:
return False
# Find the earliest timestamp in the proof list
earliest_timestamp = min(proof['timestamp'] for proof in self.proof_list)
# Define the threshold timestamp (1 second after the earliest one)
threshold_timestamp = earliest_timestamp + 1
# Filter proofs within the valid time range (<= 1 second from the earliest)
valid_proofs = [proof['proof'] for proof in self.proof_list if proof['timestamp'] <= threshold_timestamp]
# Count occurrences of each proof
proof_counts = {}
for proof in valid_proofs:
proof_counts[proof] = proof_counts.get(proof, 0) + 1
# Find the proof with the highest occurrence
max_proof = max(proof_counts, key=proof_counts.get, default=None)
return max_proof if max_proof is not None else False
Firstly, execute the proof task and collect the results. This step involves calling the prove
function on each node to collect its returned status code, proof result, and node address.These results are stored in the prove_results
list.
Error Handling
Manage potential issues such as communication errors and data validation errors.
1. Process and filter proof results
Iterate through each item in the prove_results
list. For each result:
Check if the code is equal to
GRPC_STATUS_ERROR
(indicating an error).If there is an error, log the error and relevant node information using
Logging.error
.If there are no errors, add the proof to the decision tree's
proof_list
.
2. Retrieve valid proof results from the decision tree
Call the get_proof()
method to retrieve valid proof results from the decision tree.
This method checks all received proofs and selects the most suitable proof based on timestamp and frequency.
3. Handle the final result
If the
get_proof()
method returnsFalse
, indicating no valid proof results, log this status usingLogging.error
.If there are valid proof results, log the success information and proof results using
Logging.info
.
Example code explanation
This code is used to process and validate the results returned by multiple nodes during the proof process. The specific purpose of the code is as follows:
for (code, proof, node) in prove_results:
if code == GRPC_STATUS_ERROR:
Logging.error(f"[prove] - task[{task_id}] - node[{node}] - Error reason: {proof}")
continue
proof_list.add(proof)
proof = proof_list.get_proof()
if not proof:
Logging.error(f"[prove] - task[{task_id}] - No valid proof")
else:
Logging.info(f"[prove] - task[{task_id}] Prove successfully, proof: {proof}")
This code first loops through the prove_results and processes the results returned by each node.
If the returned status code indicates an error
GRPC_STATUS_ERROR
, record the error message and continue to the next loopIf there are no errors, add the results to the decision tree.
Afterwards, try to obtain valid proof from the decision tree.
Record corresponding logs based on whether the proof has been successfully obtained.
Last updated