Last updated

Attesting Function Calls

The Blocky Attestation Service attest-fn-call command allows you to submit a function call to a TEE for execution. Below we walk you through a simple "hello world" example to use Block AS to create an attestation for a function call.

Setup

Quick Start

To run this example, call:

make run

You will see the following output extracted from a Blocky AS response:

Log:
Writing "Hello, World!" to host

Output:
Hello, World!

Walkthrough

Step 1: Create a function that returns a "Hello, World!" message.

Our first goal is to create a simple function that returns a "Hello, World!" message. We will write this function in Go and compile it to WebAssembly (WASM) to run on the Blocky AS server. If you open main.go you'll see our function there:

//export helloWorld
func helloWorld(inputPtr uint64, secretPtr uint64) uint64 {
	msg := "Hello, World!"

	basm.Log(fmt.Sprintf("Writing \"%s\" to host\n", msg))

	return basm.WriteToHost([]byte(msg))
}

You will notice a few things:

  • The helloWorld function is exported so that it can be invoked by the Blocky AS server in a WASM runtime.
  • The function takes two uint64 arguments and returns a uint64. These are fat pointers to shared memory managed by the Blocky AS server, where the first 32 bits are a memory address and the second 32 bits are the size of the data. The memory space is sandboxed and shared between the TEE host program (Blocky AS server) and the WASM runtime (your function). The inputPtr and secretPtr arguments carry user-defined function input and secrets, though we don't make use of them in this example. The output of the function is also a memory pointer, whose value will be returned to the user.
  • The function uses basm Blocky Attestation Service WASM Go SDK basm.Log function to write a message to the Blocky AS server log, maintained separately for each function invocation. You can log messages to debug or monitor your function's behavior.
  • The function calls basm.WriteToHost to write a byte array (serialized from msg) to shared memory. The host (Blocky AS server) will create an attestation over that array as a part of its response.

Step 2: Compile the function to WebAssembly (WASM)

To invoke our function in the Blocky AS server, we first need to compile it into a WASM file. If you inspect the build target in the Makefile, you'll see the build command:

@docker run --rm \
  -v .:/src \
  -w /src \
  tinygo/tinygo:0.31.2 \
  tinygo build -o tmp/x.wasm -target=wasi ./...

where we use docker to run tinygo to compile main.go to WASM and save it to tmp/x.wasm. You can build our function by calling:

make build

Step 3: Invoke the function on the Blocky AS server

To invoke our function, we first need to define an invocation template. We have one set up already in fn-call.json that looks like:

{
  "code_file": "tmp/x.wasm",
  "function": "helloWorld"
}

where code_file is the path to the WASM file we compiled earlier, and function is the name of the exported function we want to call.

To invoke our function, we need to pass the invocation template to bky-as. If you inspect the run target in the Makefile, you'll see the command:

cat fn-call.json | bky-as attest-fn-call > tmp/out.json

where we use cat to read the fn-call.json, pipe it to bky-as attest-fn-call, and save the output to tmp/out.json.

So then to run our function, you can call:

make run

The call to bky-as attest-fn-call will use the config.toml configuration file. Its host field, set to local-server, will direct bky-as to start a local, non-TEE instance of the Blocky AS server to run our function in demo mode. You can confirm this by finding the following output:

🚀 Starting local server at http://126.0.0.1:8081 ...success

If you'd like to send our function for execution on a Blocky AS server hosted on a TEE, you can follow the bky-as configuration instructions in our documentation.

Step 4: Extract function output from the Blocky AS attestation

The run target will extract the log and the attested output of the function call. After running:

make run

you should see:

Log:
Writing "Hello, World!" to host

Output:
Hello, World!

To dive deeper, let's again look at the run target in the Makefile. There you will see that we save the output of the bky-as attest-fn-call command to tmp/out.json, which contains:

{
  "enclave_attested_application_public_key": {
    "enclave_attestation": "eyJQbGF0Zm9ybSI6InBsYWl...",
    "claims": {
      "enclave_measurement": {
        "Platform": "plain",
        "Code": "plain"
      },
      "public_key": {
        "curve_type": "p256k1",
        "data": "BN3/W1LNadnQ/AUWabtYJw0z58d4fC+oN83SlI+qUYShcUnYY67tlkE8fDnOa+pRLhaiGzvFUYguCKL/Bqo5hH0="
      }
    }
  },
  "transitive_attested_function_call": {
    "transitive_attestation": "WyJXeUpaZW14cVRucFJl...",
    "claims": {
      "hash_of_code": "c9c7439eb4aa0b3905f84654c6cf07fa9edf5730f5a5252086ece67c0b4dcc50fc7605cae70849799124aaf87332ab2a31bb1b7b9bdb52233ef4850899ebd15a",
      "function": "helloWorld",
      "hash_of_input": "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
      "output": "SGVsbG8sIFdvcmxkIQ==",
      "hash_of_secrets": "9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00"
    },
    "logs": "V3JpdGluZyAiSGVsbG8sIFdvcmxkISIgdG8gaG9zdAo="
  }
}

The enclave_attested_application_public_key contains the enclave_attestation over the Blocky AS server public key. The transitive_attested_function_call section contains the transitive_attestation over the function call. The bky-as attest-fn-call command verifies that the enclave_attestation has been signed by either the TEE hardware manufacturer's private key, when the Blocky AS server is running on an AWS Nitro Enclave TEE, or by a hardcoded development private key, when the Blocky AS server is running locally. In this example, we have set up config.toml with host = "local-server", which directs the bky-as to start a local server. As a part of the verification process, the bky-as lists enclave_attested_application_public_key.claims attested by the enclave_attestation. You can confirm that enclave_attestation was produced by a local Blocky AS server by seeing that enclave_attested_application_public_key.claims.enclave_measurement.Platform is reported as plain. The bky-as CLI also checks that the measurement of the Blocky AS server code, attested by the enclave_attestation, matches one in the acceptable_measurements list in config.toml. Again, since we are running the Blocky AS server locally in this example, the enclave_attested_application_public_key.claims.enclave_measurement.Code is reported as plain. Finally, the bky-as CLI extracts the enclave attested application public key, generated by the Blocky AS server on startup, and uses it to verify the signature of the transitive_attestation and extract its claims. You can learn more about this process in the Attestations in the Blocky Attestation Service section in our documentation.

The claims section contains attested information about the execution of the function. You can see:

  • hash_of_code: The hash of the WASM code executed by the Blocky AS server
  • function: The name of the function executed by the Blocky AS server
  • hash_of_input: The hash of the input data used by the function. In this example this is the hash of the empty string, since we didn't specify any input.
  • hash_of_secrets: The hash of the secrets used by the function. In this example this is the hash of the empty string, since we didn't specify any secrets.
  • output: The output of the function encoded in base64.

Finally, the logs field contains the logs generated by the function execution and encoded in base64. Notice that while output is a part of the attested claims, the logs are not attested and are only a part of the server response.

If you look at the run target in the Makefile again, you will see that we use jq to extract the output and the logs fields from tmp/out.json and decode them from base64.