Skip to content

Commit

Permalink
Add Wise TLSN Verifier (#11)
Browse files Browse the repository at this point in the history
* Add generic tlsn verifier cargo project

* Add wise dockerfile

* Add initial wise tlsn verifier api

* fix bugs

* Refactor and add registration

* local commit

* Refactor to support new endpoints

* Fix bug

* Update signing method

* Fix include intent hash; Hash wise tag

* Deserialize proof data

* Add basic checks to extracted values before signing

* Update intent hash type to uint256

* stringify return values

* Initial Wise verifier and unit tests (#12)

* wip verifier tests

* first working test

* remove warnings

* remove warnings

* add transfer test

* refactor into own class; add more tests

* add tlsn proof verifier regex test

* add 2 matches case

* reorg api

* fix init

* add util tests

* add tests for errors

* add test transfer with note

* rm unused code

* add date nullifier for wise registration (#15)

* update readme (#16)

* Remove refundRecipientId from transfer proof and add user_address to registration_profile_id

* Update user address to address type

* Remove date from registration profile id

* Fix bug; remove profile id from transfer add back refundrecipientid to registration mc account id

* Add back profile id

* fix tests

* fix tests

* Deploy to prod

* resolve merge conflicts

---------

Co-authored-by: Richard Liang <[email protected]>
  • Loading branch information
0xSachinK and richardliang authored Apr 23, 2024
1 parent 29a7baa commit 2638d2f
Show file tree
Hide file tree
Showing 37 changed files with 5,978 additions and 5 deletions.
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
.env
received_eml/*.eml
received_eml/*.json
proofs/*.json

venv/
inputs/*.json

__pycache__/*
*/__pycache__/*
*/*/__pycache__/*
*/*/*/__pycache__/*


src/*/build/
tlsn-verifier/target
tlsn-verifier/*.txt
tlsn_verify_outputs/*.txt

src/*/build/
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use an existing Docker image as a base
FROM rust:1.76.0

# # Update the package list and install necessary dependencies
RUN apt-get update && \
apt install -y cmake build-essential pkg-config libssl-dev libgmp-dev libsodium-dev nasm


RUN mkdir -p /root/prover-api/proofs
RUN mkdir -p /root/prover-api/tlsn_verify_outputs
RUN mkdir -p /root/prover-api/tlsn-verifier

# COPY ./tlsn-verifier/target/release/tlsn-verifier /root/prover-api/tlsn-verifier/target/release/tlsn-verifier
# RUN chmod +x /root/prover-api/tlsn-verifier/target/release/tlsn-verifier

COPY ./tlsn-verifier/ /root/prover-api/tlsn-verifier
WORKDIR /root/prover-api/tlsn-verifier
RUN cargo build --release

WORKDIR /root/prover-api
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# prover-api

## Setup

1. Clone the repository
2. Setup virtual environment using `python3 -m venv venv`
3. Install the required dependencies using `pip install -r requirements.txt`
4. `cp .env.example .env` and set `.env` file within each payment type subdirectory before running tests. For `wise` and `utils`, set `CUSTOM_PROVER_API_PATH` to your local path of the prover-api directory and `VERIFIER_PRIVATE_KEY` to any private key you want to use for testing.
5. `cd tlsn-verifier` and run `cargo build --release` to build the rust verifier
6. For testing, run `pytest` while back in the root folder

## Deployment
TODO
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ modal_client>=0.56
python-dotenv==1.0.0
fastapi
requests
dkimpy
dkimpy
pytest
3 changes: 2 additions & 1 deletion src/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ modal_client>=0.56
python-dotenv==1.0.0
fastapi
requests
dkimpy
dkimpy
eth_account
26 changes: 26 additions & 0 deletions src/utils/alert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from utils.slack_utils import upload_file_to_slack

class AlertHelper():

def __init__(self, error_helper, stub_name, docker_name) -> None:
self.error_helper = error_helper
self.stub_name = stub_name
self.docker_name = docker_name

def init_slack(self, token, channel_id):
self.slack_token = token
self.slack_channel_id = channel_id

def get_fmtd_error_msg(self, error_code):
error_msg = self.error_helper.get_error_message(error_code)
return f'Alert: {error_msg}. Stub: {self.stub_name}. Docker image: {self.docker_name}'

def alert_on_slack(self, error_code, file_payload=""):
fmtd_error_msg = self.get_fmtd_error_msg(error_code)
response = upload_file_to_slack(
self.slack_channel_id,
self.slack_token,
fmtd_error_msg,
{'file': file_payload}
)
return response.status_code
20 changes: 18 additions & 2 deletions src/utils/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,43 @@
class Errors:

class ErrorCodes(Enum):
TESTING = 100
INVALID_PAYMENT_TYPE = 1
INVALID_CIRCUIT_TYPE = 2
INVALID_DOMAIN_KEY = 3
# Email
DKIM_VALIDATION_FAILED = 4
INVALID_FROM_ADDRESS = 5
INVALID_EMAIL_SUBJECT = 6
PROOF_GEN_FAILED = 7
INVALID_EMAIL = 8
INVALID_SELECTOR = 9
INVALID_SELECTOR = 9
# TLSN
TLSN_PROOF_VERIFICATION_FAILED = 10
TLSN_WISE_INVALID_TRANSFER_VALUES = 11
TLSN_WISE_INVALID_PROFILE_REGISTRATION_VALUES = 12
TLSN_WISE_INVALID_MC_ACCOUNT_REGISTRATION_VALUES = 13

def __init__(self):
self.error_messages = {
self.ErrorCodes.TESTING: "Testing 🙂",
# Api
self.ErrorCodes.INVALID_PAYMENT_TYPE: "Invalid payment type",
self.ErrorCodes.INVALID_CIRCUIT_TYPE: "Invalid circuit type. Circuit type should be send or registration",
# Email
self.ErrorCodes.INVALID_DOMAIN_KEY: "❗️Domain key might have changed❗️",
self.ErrorCodes.DKIM_VALIDATION_FAILED: "DKIM validation failed",
self.ErrorCodes.INVALID_FROM_ADDRESS: "Invalid from address",
self.ErrorCodes.INVALID_EMAIL_SUBJECT: "Invalid email subject",
self.ErrorCodes.PROOF_GEN_FAILED: "Proof generation failed",
self.ErrorCodes.INVALID_EMAIL: "Invalid email",
self.ErrorCodes.INVALID_SELECTOR: "❗️Selector might have changed❗️"
self.ErrorCodes.INVALID_SELECTOR: "❗️Selector might have changed❗️",
# TLSN
self.ErrorCodes.TLSN_PROOF_VERIFICATION_FAILED: "TLSN proof verification failed",
self.ErrorCodes.TLSN_WISE_INVALID_TRANSFER_VALUES: "TLSN invalid extracted values for `transfer`",
self.ErrorCodes.TLSN_WISE_INVALID_PROFILE_REGISTRATION_VALUES: "TLSN invalid extracted values for `profile registration`",
self.ErrorCodes.TLSN_WISE_INVALID_MC_ACCOUNT_REGISTRATION_VALUES: "TLSN invalid extracted values for `mc account registration`",

}

def get_error_message(self, error_code):
Expand Down
54 changes: 54 additions & 0 deletions src/utils/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,57 @@ def read_proof_from_local(payment_type, circuit_type, nonce):
public_values = file.read()

return proof, public_values

tlsn_verify_send_data_path = "[base_path]/tlsn_verify_outputs/send_data_[payment_type]_[circuit_type]_[nonce].txt"
tlsn_verify_recv_data_path = "[base_path]/tlsn_verify_outputs/recv_data_[payment_type]_[circuit_type]_[nonce].txt"
tlsn_proof_file_path = "[base_path]/proofs/tlsn_proof_[payment_type]_[circuit_type]_[nonce].json"

def get_tlsn_send_data_file_path(payment_type, circuit_type, nonce):
base_path = os.environ.get('CUSTOM_PROVER_API_PATH', "/root/prover-api")
return tlsn_verify_send_data_path\
.replace("[base_path]", base_path)\
.replace("[payment_type]", payment_type)\
.replace("[circuit_type]", circuit_type)\
.replace("[nonce]", nonce)

def get_tlsn_recv_data_file_path(payment_type, circuit_type, nonce):
base_path = os.environ.get('CUSTOM_PROVER_API_PATH', "/root/prover-api")
return tlsn_verify_recv_data_path\
.replace("[base_path]", base_path)\
.replace("[payment_type]", payment_type)\
.replace("[circuit_type]", circuit_type)\
.replace("[nonce]", nonce)

def get_tlsn_proof_file_path(payment_type, circuit_type, nonce):
base_path = os.environ.get('CUSTOM_PROVER_API_PATH', "/root/prover-api")
return tlsn_proof_file_path\
.replace("[base_path]", base_path)\
.replace("[payment_type]", payment_type)\
.replace("[circuit_type]", circuit_type)\
.replace("[nonce]", nonce)


def write_tlsn_proof_to_local(file_contents, payment_type, circuit_type, nonce):
file_path = get_tlsn_proof_file_path(payment_type, circuit_type, nonce)
with open(file_path, 'w') as file:
file.write(file_contents)
return file_path

def read_tlsn_verify_output_from_local(payment_type, circuit_type, nonce):
send_data = ""
recv_data = ""

send_data_file_path = get_tlsn_send_data_file_path(payment_type, circuit_type, nonce)
recv_data_file_path = get_tlsn_recv_data_file_path(payment_type, circuit_type, nonce)

# check if the file exists
if not os.path.isfile(send_data_file_path) or not os.path.isfile(recv_data_file_path):
print("Send/Recv outuput file does not exist")

with open(send_data_file_path, 'r') as file:
send_data = file.read()

with open(recv_data_file_path, 'r') as file:
recv_data = file.read()

return send_data, recv_data
43 changes: 43 additions & 0 deletions src/utils/regex_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import re
import json

## Emulating zk-regex in python
def extract_regex_values(input, regex_patterns):
matched_values = []
for pattern in regex_patterns:
match = re.search(pattern, input)
if match:
matched_values.append(match.group(1))
return matched_values


def extract_json(input_blob, start_substr, end_substr='}'):

start_index = input_blob.find(start_substr)
if start_index == -1:
return {}

end_index = input_blob.rfind(end_substr)
if end_index == -1:
return {}

json_str = input_blob[start_index:end_index+len(end_substr)]
print('json_str', json_str)
data = json.loads(json_str)
return data

def extract_json_values(input_blob, keys):
data = extract_json(input_blob, '{"id":', '}')

values = []
for key in keys:
if isinstance(key, str):
# If the key is a string, look for it directly in the data
if key in data:
values.append(data[key])
elif isinstance(key, (list, tuple)) and len(key) == 2:
# If the key is a list or tuple of length 2, navigate to the nested value
nested_key, inner_key = key
if nested_key in data and inner_key in data[nested_key]:
values.append(data[nested_key][inner_key])
return values
49 changes: 49 additions & 0 deletions src/utils/sign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import os
from web3 import Web3
from eth_abi import encode
from eth_account import Account
from eth_account.messages import encode_defunct

def encode_and_hash(args, types):
"""
Encode arguments according to specified types and hash the result using keccak256.
:param args: The values to encode.
:param types: The Solidity types of the arguments.
:return: The keccak256 hash of the encoded arguments.
"""
# Encode the arguments
encoded = encode(types, args)
print('Encode packed values', encoded)

# Hash the encoded arguments
hashed = Web3.keccak(encoded)

return hashed.hex()


def sign_values_with_private_key(env_var_name, values, types):
"""
Loads an Ethereum private key from an environment variable,
signs a concatenated string of the provided values, and returns the signature.
:param env_var_name: The name of the environment variable holding the private key.
:param values: An array of values to sign.
:return: The signature of the concatenated values.
"""
# Load the private key from the environment variable
private_key = os.getenv(env_var_name)
if not private_key:
raise ValueError(f"Environment variable '{env_var_name}' not found or empty.")

# Encode the arguments according to their specified types and hash them
message = encode_and_hash(values, types)
print('Hashed encoded message:', message)

# Sign the message
message_encoded = encode_defunct(hexstr=message)
print('Message Encoded (Signed):', message_encoded)
signed_message = Account.sign_message(message_encoded, private_key=private_key)

# Return the signature in hex format
return signed_message.signature.hex()
1 change: 1 addition & 0 deletions src/utils/tests/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CUSTOM_PROVER_API_PATH=<YOUR_CUSTOM_PROVER_API_PATH_FOR_TESTING>
Empty file added src/utils/tests/__init__.py
Empty file.
1 change: 1 addition & 0 deletions src/utils/tests/proofs/invalid_proof.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/utils/tests/proofs/swapi.json

Large diffs are not rendered by default.

Loading

0 comments on commit 2638d2f

Please sign in to comment.