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 and the Blocky Compiler by following the Getting Started instructions.
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_calldirectory:git clone git@github.com:blocky/attestation-service-examples.git cd attestation-service-examples/attest_fn_call
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
helloWorldfunction is tagged with the//exportcomment, so that it can be invoked by the Blocky AS server in a WASM runtime. - The function takes two
uint64arguments 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). TheinputPtrandsecretPtrarguments 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
basmBlocky Attestation Service WASM Go SDKWriteToHostfunction 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.wasmNote that you can speed up the build time during development by dropping the
--reproducibleflag, which will allowbky-cto 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. You can inspect it by running:
jq -r "." fn-call.json{
"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.jsonwhere 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-callwill use the config.toml configuration file. Itshostfield, set tolocal-server, will directbky-asto 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 ...successIf you'd like to send our function for execution on a Blocky AS server hosted on a TEE, you can follow the Getting Started instructions.
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. You can inspect it by running:
jq -r '.' out.json{
"enclave_attested_application_public_key": {
"enclave_attestation": "eyJwbGF0Zm9ybSI6InBsYWluIiwicGxhdGZvcm1fYXR0ZXN0YXRpb25zIjpbImV5SmtZWFJoSWpvaVpYbEthbVJZU2pKYVZqa3daVmhDYkVscWIybGpSRWt4VG0xemVFbHBkMmxhUjBZd1dWTkpOa2xyU1QwaUxDSnRaV0Z6ZFhKbGJXVnVkQ0k2ZXlKd2JHRjBabTl5YlNJNkluQnNZV2x1SWl3aVkyOWtaU0k2SW5Cc1lXbHVJbjE5IiwiZXlKa1lYUmhJam9pVlVWU05XUlVWblZYVjBWNFVrWkNUVmRVYUhoTlJrVXpXbFZTU1dGcVpHdFZiVlp2VTBkd1VsRXlUVDBpTENKdFpXRnpkWEpsYldWdWRDSTZleUp3YkdGMFptOXliU0k2SW5Cc1lXbHVJaXdpWTI5a1pTSTZJbkJzWVdsdUluMTkiLCJleUprWVhSaElqb2lXbXRvTVZZeVVrTmpiVkYyWWpGQ2ExWnJlRkZXUkVweFpWZGFVVTR5U2pOaWFtc3hXbXRzU1dWR1JUMGlMQ0p0WldGemRYSmxiV1Z1ZENJNmV5SndiR0YwWm05eWJTSTZJbkJzWVdsdUlpd2lZMjlrWlNJNkluQnNZV2x1SW4xOSIsImV5SmtZWFJoSWpvaVpGVm5NVmt6Vm5WbFIzQnNVbGhHYms0elRUTk5NVkpJWlZoYWQxbDZNR2xtVVQwOUlpd2liV1ZoYzNWeVpXMWxiblFpT25zaWNHeGhkR1p2Y20waU9pSndiR0ZwYmlJc0ltTnZaR1VpT2lKd2JHRnBiaUo5ZlE9PSIsImV5SmtZWFJoSWpvaVoxZG1WRU51VFVNNFNXZEpSMWhsV2toWGFrSTVTV3BIV0c4emNUTjBlR3BTTlRsVUwxZHNOblZZVFQwaUxDSnRaV0Z6ZFhKbGJXVnVkQ0k2ZXlKd2JHRjBabTl5YlNJNkluQnNZV2x1SWl3aVkyOWtaU0k2SW5Cc1lXbHVJbjE5Il19",
"claims": {
"enclave_measurement": {
"platform": "plain",
"code": "plain"
},
"public_key": {
"curve_type": "p256k1",
"data": "BPDyu5nYa1DPLY8q0Q7eDHj7dRehHjQCcfHuWdBrd/oPdVLPT2jyfP7bwn95fIHxQuH5cunxjeEqg7s73TGyvpc="
}
}
},
"transitive_attested_function_call": {
"transitive_attestation": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ZjY0NGU5ODE1ZmQ5NGI0NGEwMzc2NzE4NTYzMzUzMzc2Yjk5ZmVhZmQ4ZmFmZThiYTFmNTllNzQ2ZTA3M2VhMTYzODhhZmI5YmU2MzM1NjYxNThkYzYyYmM0NzJhYjhlYWEzOWU0NGQ0ZTA3MDI3OTdiZTM0YmMwNGY2MWZlYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKaGVsbG9Xb3JsZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgGE2OWY3M2NjYTIzYTlhYzVjOGI1NjdkYzE4NWE3NTZlOTdjOTgyMTY0ZmUyNTg1OWUwZDFkY2MxNDc1YzgwYTYxNWIyMTIzYWYxZjVmOTRjMTFlM2U5NDAyYzNhYzU1OGY1MDAxOTlkOTViNmQzZTMwMTc1ODU4NjI4MWRjZDI2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1IZWxsbywgV29ybGQhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAYTY5ZjczY2NhMjNhOWFjNWM4YjU2N2RjMTg1YTc1NmU5N2M5ODIxNjRmZTI1ODU5ZTBkMWRjYzE0NzVjODBhNjE1YjIxMjNhZjFmNWY5NGMxMWUzZTk0MDJjM2FjNTU4ZjUwMDE5OWQ5NWI2ZDNlMzAxNzU4NTg2MjgxZGNkMjYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQcutGwPLa+IjFuqBEkFCDv0DBOYTepc8Hy3yWZEg5pkLK/tNCPlz5sYJGCZUFm/hFEczQCnz75lKvbnaI8xsdg0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"claims": {
"hash_of_code": "9f644e9815fd94b44a0376718563353376b99feafd8fafe8ba1f59e746e073ea16388afb9be633566158dc62bc472ab8eaa39e44d4e0702797be34bc04f61fec",
"function": "helloWorld",
"hash_of_input": "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
"output": "SGVsbG8sIFdvcmxkIQ==",
"hash_of_secrets": "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"
}
}
}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,hash_of_codeis the hash of themain.wasmfile we compiled in Step 2. You can verify this by running:which outputsBUILT_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
diffcommand returns no output, it means that the hashes match.If the hashes don't match, check that you've used the
--reproducibleflag during thebky-cWASM build process.function: The name of the function executed by the Blocky AS server. To parse out the function name fromout.json, run:which outputsjq -r '.transitive_attested_function_call.claims.function' out.jsonhelloWorldhash_of_input: The hash of the input data used by the function. In this example,hash_of_inputis the hash of the empty string, since we didn't specify any input. You can verify this by running:which outputsBUILT_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,hash_of_secretsis the hash of the empty string, since we didn't specify any secrets. You can verify this by running:which outputsBUILT_SECRETS_HASH=$(echo -n "" | 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 ourhelloWorldfunction, call:which outputsjq -r '.transitive_attested_function_call.claims.output | @base64d ' out.jsonHello, World!