Skip to content

Commit

Permalink
Add: improvements on Yao's millionaires example
Browse files Browse the repository at this point in the history
  • Loading branch information
grydz committed Nov 26, 2024
1 parent 301482d commit 5e29611
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 89 deletions.
111 changes: 103 additions & 8 deletions examples/yaos_millionaires/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,121 @@
# Yao's Millionaires application example

Yao's Millionaires problem solved using Cosmian Enclave.
Each participant reprensented by its public key can send the value of its wealth to known who is the richest.
The result is encrypted for each public key.
[Yao's Millionaires' problem](https://en.wikipedia.org/wiki/Yao%27s_Millionaires%27_problem) solved using Cosmian Enclave.
Each participant reprensented by its public key can send the value of its wealth to know who is the richest.
The result is encrypted using participant's public key.

## Test your app before creating the enclave
## Prequisites

### Setup

First, each participant generates a keypair to identify with their own public/private key:

```console
$ cenclave keygen --asymmetric --output /tmp/keypair1.bin
Public key: e046f56688633e0d27ffd13127275a4278f11a6388abad043e50bbdefed5a304
Public key (Base64): 4Eb1ZohjPg0n/9ExJydaQnjxGmOIq60EPlC73v7VowQ=
Keypair wrote to /tmp/keypair1.bin
$ cenclave keygen --asymmetric --output /tmp/keypair2.bin
Public key: 8b5a7b38699f8414dfac6ccb7a3ea0faadbe66f74980f8cc5c6b52b8dd2aee12
Public key (Base64): i1p7OGmfhBTfrGzLej6g+q2+ZvdJgPjMXGtSuN0q7hI=
Keypair wrote to /tmp/keypair2.bin
```

then populate `secrets.json` with participant's public key base64-encoded:

```console
{
"participants": ["4Eb1ZohjPg0n/9ExJydaQnjxGmOIq60EPlC73v7VowQ=", "i1p7OGmfhBTfrGzLej6g+q2+ZvdJgPjMXGtSuN0q7hI="]
}
```

### Test the code locally without SGX (docker required)

```console
$ cenclave localtest --code src/ \
--dockerfile Dockerfile \
--config config.toml \
--test tests/
--test tests/ \
--secrets secrets.json
```

## Create Cosmian Enclave package with the code and the container image
### Create a package for Cosmian Enclave

```console
$ cenclave package --code src/ \
--dockerfile Dockerfile \
--config config.toml \
--test tests/ \
--output code_provider
--output tarball/
```

The generated package can now be sent to the SGX operator.
The generated tarball file in `tarball/` folder can now be used on the SGX machine properly configured with Cosmian Enclave.

Optionally use `--encrypt` if you want the code to be encrypted.

## Running the code with Cosmian Enclave

### Spawn the configuration server

```console
$ cenclave spawn --host <HOST> --port <PORT> --size <ENCLAVE_SIZE> --package <TARBALL_FILE> --output sgx_operator/ --san <EXTERNAL_IP | DOMAIN_NAME | localhost> yaos_millionaires
```

- `host`: usually 127.0.0.1 for localhost or 0.0.0.0 to expose externally
- `port`: network port used by your application, usually 9999
- `size`: memory size (in MB) of the enclave (must be a power of 2 greater than 2048)
- `package`: tarball file with the code and container image
- `san`: [Subject Alternative Name](https://en.wikipedia.org/wiki/Public_key_certificate#Subject_Alternative_Name_certificate) used for routing with SSL pass-through (either domain name, external IP address or localhost)

### Seal code secret key (optionally)

If you choose to encrypt the code with `--encrypt` then you need to verify your enclave first.
Ask the SGX operator to communicate the `evidence.json` file to do the remote attestation and verify that your code is running in the enclave:

```console
$ cenclave verify --evidence evidence.json --package tarball/package_<><>.tar --output ratls.pem
```

if successful, then include the randomly generated secret key in `secrets_to_seal.json`:

```text
{
"code_secret_key": "HEX_CODE_SECRET_KEY"
}
```

and finally seal `secrets_to_seal.json` file:

```console
$ cenclave seal --input secrets_to_seal.json --receiver-enclave ratls.pem --output code_provider/sealed_secrets.json.enc
```

### Run your application

After sending `secrets.json` and `sealed_secrets.json.enc` (if the code is encrypted) to the SGX operator, it's now ready to run:

```console
$ # add `--sealed-secrets sealed_secrets.json.enc` if needed
$ cenclave run --secrets secrets.json yaos_millionaires
```

### Use the client

In the `client/` directory you can use the Python client to query your enclave:

```console
$ # Verify the remote enclave and the MRENCLAVE hash digest
$ python main.py --verify https://<HOST>:<PORT>
$
$ # list participants
$ python main.py --keypair /tmp/keypair1.bin --list https://<HOST>:<PORT>
$
$ # push your fortune
$ python main.py --keypair /tmp/keypair1.bin --push 1_000_000 https://<HOST>:<PORT>
$ python main.py --keypair /tmp/keypair1.bin --push 2_000_000 https://<HOST>:<PORT>
$
$ # ask who is the richest with keypair1 (result encrypted for keypair1)
$ python main.py --keypair /tmp/keypair1.bin --result https://<HOST>:<PORT>
$ # ask who is the richest with keypair2 (result encrypted for keypair2)
$ python main.py --keypair /tmp/keypair2.bin --result https://<HOST>:<PORT>
```
172 changes: 129 additions & 43 deletions examples/yaos_millionaires/client/main.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,72 @@
"""Simple Python client for Yao's Millionaires.
"""Simple Python client for Yao's Millionaires with Cosmian Enclave.
```console
$ pip install requests intel-sgx-ra
$ python main.py http://127.0.0.1:5000 # for local testing
$ pip install requests intel-sgx-ra cenclave-lib-crypto
$ python main.py --help
usage: Client for Yao's Millionaires in Cosmian Enclave
positional arguments:
url URL of the remote enclave
options:
-h, --help show this help message and exit
--reset Remove participant's data from the computation
--verify Verify the enclave by doing the remote attestation
--list List participant's public key
--push NUMBER Push your wealth as number for the computation
--result Get result of the computation
--keypair PATH Path of the public/private keypair
--debug Debug information to stdout
```
"""

import argparse
import base64
import sys
import logging
import struct
import tempfile
from pathlib import Path
from typing import Any, Dict, List, Union
from typing import Any, Dict, List, Optional, Union

import requests
from cenclave_lib_crypto.seal_box import unseal
from cenclave_lib_crypto.seal_box import seal, unseal
from intel_sgx_ra.maa.attest import verify_quote
from intel_sgx_ra.quote import Quote
from intel_sgx_ra.ratls import get_server_certificate, ratls_verify_from_url, url_parse


# Key generated with cenclave_lib_crypto.x2559.x25519_keygen()
PK1: bytes = bytes.fromhex(
"938b3e0e60ebbdbbd4348ba6a0468685043d540e332c32e8bc2ca40b858ad209"
)
PK1_B64: str = "k4s+DmDrvbvUNIumoEaGhQQ9VA4zLDLovCykC4WK0gk="
SK1: bytes = bytes.fromhex(
"7957ceed56d44a384cf523619a00b2c129514daf422c0b799105fb2caa23ef97"
)

PK2: bytes = bytes.fromhex(
"ff8b983287fec6aefcf1b55e8c1efeff984e5b8dfa8d4de62df521bd6ec57d14"
)
PK2_B64: str = "/4uYMof+xq788bVejB7+/5hOW436jU3mLfUhvW7FfRQ="
SK2: bytes = bytes.fromhex(
"05e5aa1c56ec3d6bf707893e6a038a825d80a2802fdb565fd8fecb840735a954"
)


def reset(session: requests.Session, url: str) -> None:
response: requests.Response = session.delete(url)

if response.status_code != 200:
raise Exception(f"Bad response: {response.status_code}")


def push(session: requests.Session, url: str, pk: bytes, n: Union[int, float]) -> None:
def push(
session: requests.Session,
url: str,
pk: bytes,
n: Union[int, float],
enclave_pk: Optional[bytes] = None,
) -> None:
encoded_n: bytes = struct.pack("<d", float(n))

if enclave_pk is not None:
encrypted_n: bytes = seal(encoded_n, enclave_pk)
data = {
"encrypted": True,
"n": base64.b64encode(encrypted_n).decode("utf-8"),
}
else:
data = {"encrypted": False, "n": base64.b64encode(encoded_n).decode("utf-8")}

response: requests.Response = session.post(
url=url,
json={"pk": base64.b64encode(pk).decode("utf-8"), "n": float(n)},
json={
"pk": base64.b64encode(pk).decode("utf-8"),
"data": data,
},
)

if response.status_code != 200:
Expand Down Expand Up @@ -79,44 +97,112 @@ def richest(session: requests.Session, url: str, pk: bytes, sk: bytes) -> str:

encrypted_content: bytes = base64.b64decode(content["max"])

print(f"Encrypted content for {pk_b64}: {encrypted_content.hex()}")
logging.debug("Encrypted content for %s: %s", pk_b64, encrypted_content.hex())

pk_winner: bytes = unseal(encrypted_content, sk)

return base64.b64encode(pk_winner).decode("utf-8")


def main() -> int:
url: str = sys.argv[1]
def verify(url: str) -> tuple[Path, bytes, bytes]:
hostname, port = url_parse(url)

session: requests.Session = requests.Session()

quote: Quote = ratls_verify_from_url(url)
ratls_cert_path: Path = Path(tempfile.gettempdir()) / "ratls.pem"
ratls_cert = get_server_certificate((hostname, port))
ratls_cert_path.write_bytes(ratls_cert.encode("utf-8"))

_: Dict[str, Any] = verify_quote(quote)
maa_result: Dict[str, Any] = verify_quote(quote)

session.verify = f"{ratls_cert_path}"
logging.debug("Microsoft Azure Attestation response: %s", maa_result)

p_1 = (PK1, 100_398)
p_2 = (PK2, 100_399)
mr_enclave: bytes = bytes.fromhex(maa_result["x-ms-sgx-mrenclave"])
enclave_pk: bytes = quote.report_body.report_data[32:]

return ratls_cert_path, mr_enclave, enclave_pk


def cli_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
usage="Client for Yao's Millionaires in Cosmian Enclave"
)

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"--reset",
action="store_true",
help="Remove participant's data from the computation",
)
group.add_argument(
"--verify",
action="store_true",
help="Verify the enclave by doing the remote attestation",
)
group.add_argument(
"--list", action="store_true", help="List participant's public key"
)
group.add_argument(
"--push",
type=float,
metavar="NUMBER",
help="Push your wealth as number for the computation",
)
group.add_argument(
"--result", action="store_true", help="Get result of the computation"
)
parser.add_argument(
"--keypair",
type=Path,
metavar="PATH",
help="Path of the public/private keypair",
)
parser.add_argument(
"--debug", action="store_true", help="Debug information to stdout"
)
parser.add_argument("url", help="URL of the remote enclave")

return parser.parse_args()


def main() -> int:
args: argparse.Namespace = cli_args()

logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)

url: str = args.url

session: requests.Session = requests.Session()

ratls_cert_path, mr_enclave, enclave_pk = verify(url)

session.verify = f"{ratls_cert_path}"

reset(session, url)
if args.reset:
reset(session, url)
logging.info("Reset success")

for pk, n in (p_1, p_2):
push(session, url, pk, n)
if args.verify:
logging.info(
"Verification successful, MRENCLAVE: %s",
mr_enclave.hex(),
)
if args.list:
logging.info(participants(session, url))

print(participants(session, url))
keypair: bytes = Path(args.keypair).read_bytes()

p_1_result = richest(session, url, PK1, SK1)
p_2_result = richest(session, url, PK2, SK2)
pk, sk = keypair[:32], keypair[32:]

assert p_1_result == p_2_result
if args.push:
push(session, url, pk, float(args.push), enclave_pk)
logging.info(
"Pushed %s with public key %s",
float(args.push),
base64.b64encode(pk).decode("utf-8"),
)

print(f"The richest participant is {p_1_result}")
if args.result:
logging.info("The richest participant is %s", richest(session, url, pk, sk))

return 0

Expand Down
4 changes: 3 additions & 1 deletion examples/yaos_millionaires/secrets_to_seal.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{}
{
"code_secret_key": "HEX_CODE_SECRET_KEY"
}
Loading

0 comments on commit 5e29611

Please sign in to comment.