Last updated

Attesting Function Calls

Blocky Attestation Service (Blocky AS) allows you to run custom functions in a Trusted Execution Environment (TEE) and receive attestations over their execution. In this example, we show you how to attest a simple "Hello, World!" function call using the Blocky AS CLI.

Prerequisites

Quick Start

To attest a simple function call using Blocky AS, follow these steps:

  1. Define a helloWorld function in main.go:

    //export helloWorld
    func helloWorld(inputPtr uint64, secretPtr uint64) uint64 {
    	msg := "Hello, World!"
    
    	return basm.WriteToHost([]byte(msg))
    }
  2. Compile main.go to WebAssembly (WASM):

    bky-c build --reproducible . ./main.wasm
  3. Invoke the function on a Blocky AS server with the bky-as CLI and save its response to out.json:

    cat fn-call.json | bky-as attest-fn-call > out.json
  4. Parse the attested function call output from out.json:

    jq -r '.transitive_attested_function_call.claims.output | @base64d ' out.json

    which will print the output of the helloWorld function:

    Hello, World!

Full Walkthrough

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

Let's 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 look at main.go in this example's repository folder, you'll see our function there:

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

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

A few things to notice:

  • The helloWorld function is tagged with the //export comment, 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 calls the basm Blocky Attestation Service WASM Go SDK WriteToHost function 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 need to compile it to WASM. If you inspect the main.wasm target in the Makefile, you'll see a series of commands that compile main.go to the main.wasm binary. You can build our function by calling:

bky-c build --reproducible . ./main.wasm

Note that you can speed up the build time during development by dropping the --reproducible flag, which will allow bky-c to cache certain artifacts at the cost of reproducibility.

Step 3: Invoke the function on the Blocky AS server

To invoke our function, we need to define an invocation template. We set up the template in fn-call.json, which contains:

{
  "code_file": "main.wasm",
  "function": "helloWorld"
}

where code_file is the path to the WASM we built in the previous step, 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 CLI by calling:

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

where we read and pipe the contents of fn-call.json to the bky-as attest-fn-call command. The bky-as CLI will invoke our function on a Blocky AS server and save its response to out.json.

Note that 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 dev mode. You can confirm this by finding the following output:

🚀 Starting local server at http://127.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

To extract the attested output of our helloWorld function, we need to parse the out.json file generated in the previous step. The out.json file will contain data similar to:

{
  "enclave_attested_application_public_key": {
    "enclave_attestation": "eyJQbGF0Zm9ybSI6InBsY...",
    "claims": {
      "enclave_measurement": {
        "platform": "plain",
        "code": "plain"
      },
      "public_key": {
        "curve_type": "p256k1",
        "data": "BCwe5u7yz27GmJ4AakUAfDJHV7MRZnmvUh3apJ7HIuao2T/bh01pC20/h5SIhJGrCRV/znFAeh2XTJ/PyneJ5yA="
      }
    }
  },
  "transitive_attested_function_call": {
    "transitive_attestation": "AAAAAAAAAAAAAAAAAAAA...",
    "claims": {
      "hash_of_code": "9f644e9815fd94b44a0376718563353376b99feafd8fafe8ba1f59e746e073ea16388afb9be633566158dc62bc472ab8eaa39e44d4e0702797be34bc04f61fec",
      "function": "helloWorld",
      "hash_of_input": "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
      "output": "SGVsbG8sIFdvcmxkIQ==",
      "hash_of_secrets": "9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00"
    }
  }
}

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 (ABI-encoded in the above example) and extract its claims. You can learn more about this process in the Attestations in the Blocky Attestation Service and Verification Process sections in our documentation.

Step 5: Verify transitive attested function call output

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. In this example, this is the hash of the main.wasm file we compiled in Step 2. You can verify this by running:

    BUILT_WASM_HASH=$(openssl dgst -sha3-512 main.wasm | awk '{print $2}')
    ATTESTED_WASM_HASH=$(jq -r '.transitive_attested_function_call.claims.hash_of_code' out.json)
    diff <(echo "$BUILT_WASM_HASH") <(echo "$ATTESTED_WASM_HASH")

    When the diff command returns no output, it means that the hashes match.

    If the hashes don't match, check that you've used the --reproducible flag during the bky-c WASM build process.

  • function: The name of the function executed by the Blocky AS server. To parse out the function name from out.json, run:

    jq -r '.transitive_attested_function_call.claims.function' out.json
  • 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. You can verify this by running:

    BUILT_INPUT_HASH=$(echo -n "" | openssl dgst -sha3-512 | awk '{print $2}')
    ATTESTED_INPUT_HASH=$(jq -r '.transitive_attested_function_call.claims.hash_of_input' out.json)
    diff <(echo "$BUILT_INPUT_HASH") <(echo "$ATTESTED_INPUT_HASH")
  • hash_of_secrets: The hash of the decrypted secrets used by the function. In this example this is the hash of the serialized JSON empty value, null, since we didn't specify any secrets. You can verify this by running:

    BUILT_SECRETS_HASH=$(echo -n 'null' | openssl dgst -sha3-512 | awk '{print $2}')
    ATTESTED_SECRETS_HASH=$(jq -r '.transitive_attested_function_call.claims.hash_of_secrets' out.json)
    diff <(echo "$BUILT_SECRETS_HASH") <(echo "$ATTESTED_SECRETS_HASH")
  • output: The output of the function encoded in base64. To parse out the output of our helloWorld function, call:

    jq -r '.transitive_attested_function_call.claims.output | @base64d ' out.json

    You should see the expected output of out helloWorld function:

    Hello, World!