Getting started

Hello World!

To demonstrate tServices, we begin with a “Hello World!” example: Yao's Millionaire Problem.

There are two millionaires, Alice and Bob, who are interested in knowing which of them is richer without revealing their actual wealth.

What we need in The Millionaire Problem is a mutually-trusted 3rd party. Smart contracts are designed for trust. However, they lack the necessary privacy and/or performance required in many use cases like DeFi, AI/ML and gaming. And in the case of the Millionaire Problem, the issue is privacy.

To implement such a privacy-preserving, mutually-trusted 3rd party on Taxa Network, the logic is straightforward under an interactive, privacy-preserving tService:

hello world

Step 1: Both parties submit their secret values to a tService, which serves as a "judge" to compare their values while maintaining their privacy. They will also both notify the tService whom they want to compare their wealth to. (This is an entrance called /submit_value)

Step 2: After both parties have submitted their values, they can request results from the "judge". (This is an entrance called /get_results)

Note: The entire tService Python code is interpreted and executed inside a hardware-isolated memory region called an enclave, which is protected by the TEE technology. The code is transparent and verifiable to every user, while the request/response data is opt-out privacy. It is confidential by default--unless the user manually reveals it. Only the user and the enclave have access to the request/response plaintext. Anyone else, including the service provider (i.e., Taxa node operator), won’t be able to decrypt the plaintext data.

The tService for solving The Millionaire Problem is as follows:

# millionaire_tservice.py
import binascii

@taxa.route("/submit")
def submit():
    rawData = request.data
    my_id = binascii.b2a_base64(taxa.globals.getUserCert())
    my_value = rawData["value"]
    session[my_id] = my_value
    response.add(taxa.globals.getUserCert())

@taxa.route("/reveal")
def reveal():
    rawData = request.data
    my_id = binascii.b2a_base64(taxa.globals.getUserCert())
    my_opponent = rawData["opponent"]

    if my_opponent not in session:
        response.add("Opponent doesn't exist")
        return

    if session[my_id] >= session[my_opponent]:
        response.add("Your value is no less than your opponent")
    else:
        response.add("Your value is less than your opponent")
    return

Now we will go through some important concepts in detail.

Key concepts

AppID

AppID is the identity for a tService which is generated by hashing the developer generated tService code. This is shown as the "address" part in a URL. The AppID utilizes multihash as its hashing algorithm. By default, the SHA-256 hash function is used.

Function

A tService can include multiple functions and any function could serve as an entrance for the code. Functions are mapped to requests using the taxa.route() decorator in Python.

URL

The user's request is initiated with a URL, which contains the AppID of the tService and an entry function.

Example:

taxa://QmWPypqFkmHYEwB61g8FNmGLvXAGraLZ6yzxM4qG61g6nz/submit_value

Request

The request is json-formatted data. By default, the user's request data is encrypted with the AES128 key acquired from remote attestation. For details see the communication section

Response

The response is also json-formatted data. By default, the user's response data is also encrypted with the AES128 key acquired from remote attestation. For details see the communication section

User Identity

Each tService user is identified via Elliptic Curve Digital Signature Algorithm (ECDSA) keypairs. The public key is called cert file, and the 256-bit private key is called key file. The cert file can be shared safely among users outside of tServices.

As is shown in the Hello World example, the global variable taxa.global.getUserCert() can return the current user's public key, which serves as the identity for each user.

Session

The session is a short-term storage data structure, organized by a key-value pair-based, that is purged after seven days. The session is good for maintaining the context during a user interaction cycle, but should not be relied upon for permanent storage. Developers should prepare exception handling for the circumstances in which the session is missing. While the session exists, Taxa guarantees its integrity and confidentiality.

Session files are encrypted and stored on Taxa nodes (by TEE's native "sealing" feature), and only the authorized enclave can decrypt it and recover the application data. The session object’s domain of access is bonded to the AppID and isolated from the other AppIDs.

Run your first tService

Once you receive access to the devnet SDK, you are ready to start developing your first tService with Pyxa. Before you start however, make sure that you have done the node connection and remote attestation.

To execute a tService on Taxa Network, first, you need to install the python SDK:

pip install taxa_sdk

Open a new file called millionaire_sdk.py and insert the following code:

# millionaire_sdk.py
import binascii
from taxa_sdk import TaxaRequest

request1 = TaxaRequest("path/to/millionaire1.json")
response1 = request1.send(
  code_path="path/to/millionaire_tservice.py",
  function="submit",
  data={"value": 2300000}
)
print("Millionaire 1 submit response:", response1['decrypted_data'])

request2 = TaxaRequest("path/to/millionaire2.json")
response2 = request2.send(
  code_path="path/to/millionaire_tservice.py",
  function="submit",
  data={"value": 1800000}
)
print("Millionaire 2 submit response:", response2['decrypted_data'])

# reveal

opponent_key2 = binascii.b2a_base64(request2.key_manager.client_cert).decode()
response3 = request1.send(
  function="reveal",
  data={"opponent": opponent_key2}
)
print("Millionaire 1 reveal:", response3['decrypted_data'])

opponent_key1 = binascii.b2a_base64(request1.key_manager.client_cert).decode()
response4 = request2.send(
  function="reveal",
  data={"opponent": opponent_key1}
)
print("Millionaire 2 reveal:", response4['decrypted_data'])

The file millionaire_tservice.py, posted above, is the code for the millionaire problem.

When you execute millionaire_sdk.py, the output should be as follows:

Millionaire 1 submit response: OK
Millionaire 2 submit response: OK
Millionaire 1 reveal: Your value is no less than your opponent
Millionaire 2 reveal: Your value is less than your opponent

Also, there should be a file called millionaire1.json and millionaire2.json generated in the paths you specified in the millionaire_sdk.py. This json file contains all the keys you need to communicate with the taxa tService. If you pass in a non-existent file, the keys will be generated for you. If the file is not empty then it will use the keys found in that file.

If you want a more detailed output, you can enable verbose mode on the request objects as follows:

request1 = TaxaRequest("path/to/millionaire1.json", verbose=True)

With this mode enabled, you can see the details of the attestation process working.

If you execute millionaire_sdk.py a second time, there may be less output because the keys have already been created and the attestation has already been performed. Although, the IP of the node server that is used can sometimes change between executions. Whenever the taxa server IP changes, attestation has to be performed again.