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
Install the Blocky AS CLI
Install the Blocky Compiler
Make sure you have Docker, jq, and openssl dgst installed on your system.
Clone the Blocky AS examples repository and navigate to the
attest_fn_call
directory.git clone git@github.com:blocky/attestation-service-examples.git cd attestation-service-examples/attest_fn_call
Quick Start
To attest a simple function call using Blocky AS, follow these steps:
Define a
helloWorld
function in main.go://export helloWorld func helloWorld(inputPtr uint64, secretPtr uint64) uint64 { msg := "Hello, World!" return basm.WriteToHost([]byte(msg)) }
Compile main.go to WebAssembly (WASM):
bky-c build --reproducible . ./main.wasm
Invoke the function on a Blocky AS server with the
bky-as
CLI and save its response toout.json
:cat fn-call.json | bky-as attest-fn-call > out.json
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 auint64
. 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). TheinputPtr
andsecretPtr
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 SDKWriteToHost
function to write a byte array (serialized frommsg
) 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 allowbky-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. Itshost
field, set tolocal-server
, will directbky-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 themain.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 thebky-c
WASM build process.function
: The name of the function executed by the Blocky AS server. To parse out the function name fromout.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 ourhelloWorld
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!