Verifying Function Execution
In the Attesting Function Calls tutorial, we described how to use the Blocky AS CLI to attest a function call. The result was an enclave attestation and a transitive attestation that work together to attest the function execution. In this tutorial, we go over how to verify the claims made by these attestations to trust an execution of an attested function call.
Prerequisites
Install the Blocky AS CLI by following the Getting Started instructions.
Make sure you have Docker, jq, and yq installed on your system.
Create a
verification
directory and navigate into it by running:mkdir -p verification cd verification
(Optional) Review the Attesting Function Calls tutorial.
(Optional) Review Blocky AS Concepts page.
Overview
Trusting the execution of an attested function call means verifying that the function code was executed faithfully on given inputs and secrets, producing a specific output, while ensuring that intermediate function state and secrets are not leaked.
Let's define these goals more precisely as a set of guarantees:
- Guarantee 1: Attested function output is the result of an execution of a specific function on given inputs and secrets.
- Guarantee 2: The intermediate state of the function execution is not tampered with.
- Guarantee 3: The intermediate state of the function execution is not leaked.
- Guarantee 4: The secrets passed to the function are not leaked.
The Blocky AS attestations make these guarantees concrete through a set of claims. In the Attesting Function Calls tutorial we obtained out.json
that contains an enclave attestation enclave_attested_application_public_key
and a transitive attestation transitive_attested_function_call
. In the Attesting Function Calls tutorial we invoked a function on a local server running in dev mode, i.e. not on a TEE enclave. For this tutorial, we've prepared out.json containing attestations over the same "Hello World" function produced by a Blocky AS server running on an AWS Nitro Enclave TEE. Download it by running:
curl -o out.json https://docs.blocky.rocks/v0.1.0-beta.12/out.json
We can inspect the claims of the attestations in out.json by running
jq '.enclave_attested_application_public_key.claims.enclave_measurement' out.json
jq '.transitive_attested_function_call.claims' out.json
{
"platform": "nitro",
"code": "933fdfbf5a849ed07c0a8201f8813b8ab976f72e93722b3e4a28dbb41d2741fb581604845b6272b04a0fa5a8b59647bd.4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493.7e1fadfbec59aba9d45e3042fd322d5e61b3e849d7c7ea20084d946fce976d48773ae723c0e57e2d6311ff3667a3a8b5"
}
{
"hash_of_code": "9f644e9815fd94b44a0376718563353376b99feafd8fafe8ba1f59e746e073ea16388afb9be633566158dc62bc472ab8eaa39e44d4e0702797be34bc04f61fec",
"function": "helloWorld",
"hash_of_input": "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
"output": "SGVsbG8sIFdvcmxkIQ==",
"hash_of_secrets": "9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00"
}
Together, these attestations claim that a Blocky AS server with an enclave image measurement code
ran on a platform
(in this case an AWS Nitro Enclave TEE) and invoked a function
(in this case helloWorld
) on a WASM binary with a hash of hash_of_code
using an input with a hash of hash_of_input
and secrets with a hash of hash_of_secrets
, producing an output encoded in base64 in the output
field (in this case, SGVsbG8sIFdvcmxkIQ==
, which decodes to Hello, World!
).
In this tutorial, we go over how to verify these claims are subject to the guarantees we defined above, and so how to trust the execution of the attested function call.
Verification Process
Step 1: Identify Blocky AS Release
To verify the claims made by the attestations, we need to identify the Blocky AS server implementation that produced the attestations. Let's parse out the attested Blocky AS code
and runtime platform
from the enclave_attested_application_public_key
enclave attestation in out.json by running:
jq -r '.enclave_attested_application_public_key.claims.enclave_measurement.platform' out.json > enclave_platform.txt
cat enclave_platform.txt
jq -r '.enclave_attested_application_public_key.claims.enclave_measurement.code' out.json > enclave_code.txt
cat enclave_code.txt
nitro
933fdfbf5a849ed07c0a8201f8813b8ab976f72e93722b3e4a28dbb41d2741fb581604845b6272b04a0fa5a8b59647bd.4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493.7e1fadfbec59aba9d45e3042fd322d5e61b3e849d7c7ea20084d946fce976d48773ae723c0e57e2d6311ff3667a3a8b5
The value in enclave_platform.txt
indicates the runtime platform of the Blocky AS server. In this example, it is nitro
, indicating that the Blocky AS server was running on an AWS Nitro Enclave TEE. The value in enclave_code.txt
is a measurement of the enclave image containing the Blocky AS server application running inside a TEE.
These values allow us to identify the Blocky AS server implementation that produced the attestations out.json. On the Releases page we list platform
and code
measurements for each Blocky AS release. We can download the code_measurement.toml file for the v0.1.0-beta.12
release by running:
curl -o code_measurement.toml https://docs.blocky.rocks/v0.1.0-beta.12/code_measurement.toml
and extract its measurements by running:
yq '.code_measurement[0].platform' code_measurement.toml > release_platform.txt
cat release_platform.txt
yq '.code_measurement[0].code' code_measurement.toml > release_code.txt
cat release_code.txt
nitro
933fdfbf5a849ed07c0a8201f8813b8ab976f72e93722b3e4a28dbb41d2741fb581604845b6272b04a0fa5a8b59647bd.4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493.7e1fadfbec59aba9d45e3042fd322d5e61b3e849d7c7ea20084d946fce976d48773ae723c0e57e2d6311ff3667a3a8b5
Now we can compare these measurements with:
diff enclave_platform.txt release_platform.txt
diff enclave_code.txt release_code.txt
When diff
calls produce no output, it means that the values are the same, and so the enclave attestation in out.json attests that it was produced by the Blocky AS server code available in the v0.1.0-beta.12
release.
Step 2: Identifying Release Code
Next, we need to identify the Blocky AS server source code behind the release. We use the v0.1.0-beta.12
release information, from the previous step, to form a S3 bucket URL, from which we can download and unpack the source code archive:
aws s3 cp s3://enclave-archive/$(cat release_platform.txt)/$(cat release_code.txt) delphi.tar.gz
tar -xzf delphi.tar.gz
The resulting delphi
folder contains the Blocky AS server source code in delphi/src.tgz
archive. We can use it to build the Blocky AS server enclave image and compute its measurement by running:
make -C delphi build
The reproducible build process produces a description of the enclave image file (delphi/output/eif-description.json
), which contains its measurements. We can parse these measurements and combine them into a single string by running:
jq -r '.Measurements | [.PCR0, .PCR1, .PCR2] | map(select(. != null)) | join(".")' delphi/output/eif-description.json > built_code.txt
cat built_code.txt
We can now compare the built_code.txt
with release_code.txt
parsed from the enclave_attested_application_public_key
.
diff built_code.txt release_code.txt
When diff
produces no output, the measurement of the enclave image file we built from source matches the one of the release.
Note that
delphi/LICENSES/BLOCKY-SARL-1_0.txt
contains source-available license for the Blocky AS server code, which allows you to inspect the code and to build it for the purpose of computing its measurements. It does not, however, allow you to modify, redistribute, or run the code.
Step 3: Inspect Blocky AS Implementation
For the claims made by the attestations to be meaningful, we need to inspect Blocky AS server code for each release to validate several properties of its implementation. These properties are:
- Property 1: Blocky AS server generates a unique public/private key pair on startup and retains control of the private key.
- Property 2: Blocky AS server enclave attestations attest the unique public key.
- Property 3: Blocky AS server uses its private key to sign transitive attestations of function calls executed by the Blocky AS server.
- Property 4: Blocky AS executes WASM binaries and faithfully attests the invoked function, its inputs, secrets, output, and hash of the WASM binary.
- Property 5: Blocky AS server does not allow tampering with the intermediate state of the function execution.
- Property 6: Blocky AS server does not leak the intermediate state of the function execution.
- Property 7: Blocky AS server does not leak the secrets passed to the function.
To verify the claims made by the attestations, we need to verify their signatures. As described on the Concepts page, Blocky AS generates a new public/private key pair encl_app_pub_key/encl_app_priv_key
on startup. The enclave attestation enclave_attested_application_public_key
attests the encl_app_pub_key
and is signed by the TEE hardware manufacturer's private key tee_priv_key
, which is the root of trust for TEE enclaves. The transitive attestation transitive_attested_function_call
, attesting the execution of a function, is signed by the encl_app_priv_key
attested in enclave_attested_application_public_key
.
Note that depending on your function implementation, you may need to verify additional properties, such as whether Blocky AS correctly implements random number generation, timestamp generation, and HTTPS request handling.
We can then validate Properties 1 through 7 by inspecting the source code in the delphi/src.tgz
archive. The validation of these properties should be done once per Blocky AS release and is akin to inspecting and auditing the implementation of a smart contract or of a blockchain node and relies on the availability of our code. This process can be done collectively by Blocky's users as well as auditors. If you'd like help in understanding a Blocky AS server implementation, please reach out to us on Telegram @blocky_rocks.
Step 4: Verify Attestation Signatures
To verify the claims made by the attestations, we need to verify their signatures. As validated in Step 2, Blocky AS generates a unique public/private key pair encl_app_pub_key/encl_app_priv_key
on startup. The enclave attestation enclave_attested_application_public_key
attests the encl_app_pub_key
and is signed by the TEE hardware manufacturer's private key tee_priv_key
, which is the root of trust for TEE enclaves. The transitive attestation transitive_attested_function_call
, attesting the execution of a function, is signed by the encl_app_priv_key
attested in enclave_attested_application_public_key
.
The out.json produced by the bky-as attest-fn-call
command is verified on output. So, if you've obtained out.json from bky-as
yourself, you know that
enclave_attested_application_public_key
signature is valid against the TEE hardware manufacturer's public keytee_pub_key
embedded in the Blocky AS CLItransitive_attested_function_call
signature is valid against theencl_app_pub_key
attested inenclave_attested_application_public_key
transitive_attested_function_call.claims
come from parsingtransitive_attested_function_call.transitive_attestation
and can proceed to Relating Attestation Claims to Blocky AS Guarantees.
We may also use the code in the release archive to build the
bky-as
CLI and compare its hash against the one installed on your machine. By inspecting the CLI code, you can convince yourself that it users the correct TEE root of trust public key and the correct processes to parse and verify Blocky AS attestations.
If you received out.json from a third party, or if you archived enclave_attested_application_public_key
and transitive_attested_function_call
in long-term storage, you may want to verify the signatures of the attestations yourself.
The easiest way to verify these signatures is to use the bky-as
CLI. If you went through the Getting Started process, you should already have a config.toml
configured with the acceptable_measurements
for the current release. If you haven't, you can download a config file for this tutorial by running:
curl -o config.toml https://docs.blocky.rocks/v0.1.0-beta.12/config.toml
To verify previously obtained attestations, simply extract the raw enclave and transitive attestations from out.json, or your long-term storage, and pass them to bky-as verify-fn-call
command by running:
jq '{
enclave_attested_application_public_key: .enclave_attested_application_public_key.enclave_attestation,
transitive_attested_function_call: .transitive_attested_function_call.transitive_attestation
}' out.json | bky-as verify-fn-call > verified.json
cat verified.json
{"enclave_attested_application_public_key":{"enclave_attestation":"eyJwbGF0Zm9ybSI6Im5pdHJvIiwicGxhdGZvcm1fYXR0ZXN0YXRpb25zIjpbImhFU2hBVGdpb0ZrUmViOXBiVzlrZFd4bFgybGtlQ2RwTFRBMU5EQTNaak5pWW1Sa1ltUTFZakV5TFdWdVl6QXhPVGhsTnpVeU9UUmxOMk5sWkRGbVpHbG5aWE4wWmxOSVFUTTROR2wwYVcxbGMzUmhiWEFiQUFBQm1PNWdlbzlrY0dOeWM3QUFXRENUUDkrL1dvU2UwSHdLZ2dINGdUdUt1WGIzTHBOeUt6NUtLTnUwSFNkQisxZ1dCSVJiWW5Ld1NnK2xxTFdXUjcwQldEQkxUVnMyWWJQdndTa2drQXlBNFNia3puZzhVaTNtd0NvcVcvZXZPaXVUSjdobmR2R0k1TDRjSEVCS0VwMjlwSk1DV0RCK0g2Mzc3Rm1ycWRSZU1FTDlNaTFlWWJQb1NkZkg2aUFJVFpSdnpwZHRTSGM2NXlQQTVYNHRZeEgvTm1lanFMVURXREFjK0ppKzV2NXI0RUQ5d291bExKbndudlg1ejZiMVIyekF3Sy9haldhd2lpdnBvYnhQaXd4UklMQ3BHVmxYV2JRRVdEQ2JiMWJlV05EZk5xWWZpWTZlSUhHdXhNWVpyNzdBSnpmNFFkMHg4aEpVREZOZldXbGpXc1oxMUxCVlJEbGh1MzBGV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUdXREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBSFdEQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFJV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUpXREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBS1dEQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFMV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU1XREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBTldEQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFPV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQVBXREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCclkyVnlkR2xtYVdOaGRHVlpBbjh3Z2dKN01JSUNBYUFEQWdFQ0FoQUJtT2RTbE9mTzBRQUFBQUJvcjdabE1Bb0dDQ3FHU000OUJBTURNSUdPTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tWMkZ6YUdsdVozUnZiakVRTUE0R0ExVUVCd3dIVTJWaGRIUnNaVEVQTUEwR0ExVUVDZ3dHUVcxaGVtOXVNUXd3Q2dZRFZRUUxEQU5CVjFNeE9UQTNCZ05WQkFNTU1Ha3RNRFUwTURkbU0ySmlaR1JpWkRWaU1USXVkWE10ZDJWemRDMHlMbUYzY3k1dWFYUnlieTFsYm1Oc1lYWmxjekFlRncweU5UQTRNamd3TVRVeU16UmFGdzB5TlRBNE1qZ3dORFV5TXpkYU1JR1RNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1YyRnphR2x1WjNSdmJqRVFNQTRHQTFVRUJ3d0hVMlZoZEhSc1pURVBNQTBHQTFVRUNnd0dRVzFoZW05dU1Rd3dDZ1lEVlFRTERBTkJWMU14UGpBOEJnTlZCQU1NTldrdE1EVTBNRGRtTTJKaVpHUmlaRFZpTVRJdFpXNWpNREU1T0dVM05USTVOR1UzWTJWa01TNTFjeTEzWlhOMExUSXVZWGR6TUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVHNUJUZDJEdmxCb3pSK1llMGtJOXBkUVp1dC9qRXNpMnFtVUFpSnB2MVVLK1VaTjJvTGx2STZHSXVSWEVOTVI0RzJjdk9yaGR6Q0NJMTdoTkNPKzFSYXBDcGN2RzhmYkhsVHA4ektTMzFKWVZRdmtyOWFLR1l2ZUtQdEwxbVI5ZG94MHdHekFNQmdOVkhSTUJBZjhFQWpBQU1Bc0dBMVVkRHdRRUF3SUd3REFLQmdncWhrak9QUVFEQXdOb0FEQmxBakJLaEV6VnY5VzZyTVNIYWFGNFZzWlF5Uit1NjYzck82ZmhhbVB3M1pPSnNpUVl1T0VIUUxwbWU2NDEwdGZyN2kwQ01RQ3lOTDdDamhyUkZNc0JuZnN6MktDTm43NEZiY3JIWTZVc3RDc2t3eEhYaUF6bmtKQ0g5K0JHTGcreVgwRHEzSHRvWTJGaWRXNWtiR1dFV1FJVk1JSUNFVENDQVphZ0F3SUJBZ0lSQVBreGRXZ2JrSy9oSFViTXRPVG4rRll3Q2dZSUtvWkl6ajBFQXdNd1NURUxNQWtHQTFVRUJoTUNWVk14RHpBTkJnTlZCQW9NQmtGdFlYcHZiakVNTUFvR0ExVUVDd3dEUVZkVE1Sc3dHUVlEVlFRRERCSmhkM011Ym1sMGNtOHRaVzVqYkdGMlpYTXdIaGNOTVRreE1ESTRNVE15T0RBMVdoY05ORGt4TURJNE1UUXlPREExV2pCSk1Rc3dDUVlEVlFRR0V3SlZVekVQTUEwR0ExVUVDZ3dHUVcxaGVtOXVNUXd3Q2dZRFZRUUxEQU5CVjFNeEd6QVpCZ05WQkFNTUVtRjNjeTV1YVhSeWJ5MWxibU5zWVhabGN6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQlB3Q1ZPdW1DTUh6YUhEaW10cVF2a1k0TXBKemJvbEwvL1p5MllsRVMxQlI1VFNrc2ZiYjQ4QzhXQm95dDdGMkJ3N2VFdGFhUCtvaEcyYm5Vczk5MGQwSlgyOFRjUFFYQ0VQWjNCQUJJZVRQWXdFb0NXWkVoOGw1WW9Rd1RjVS85S05DTUVBd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWtDVzFEZGtGUitlV3c1YjZjcDNQbWFuZlM1WXdEZ1lEVlIwUEFRSC9CQVFEQWdHR01Bb0dDQ3FHU000OUJBTURBMmtBTUdZQ01RQ2pmeStSb2NtOVh1ZTRZbndXbU5KVkE0NGZBMFA1VzJPcFlvdzlPWUNWUmFFZXZMOHVPMVhZcnU1eHRNUFdyZk1DTVFDaTg1c1dCYkp3S0tYZFM2QnB0UUZ1WmJUNzNvL2dCaDFxVXhsL25OcjEyVU84WWZ3cjZ3UExiKzZOSXdMejMvWlpBc0V3Z2dLOU1JSUNSS0FEQWdFQ0FoQVJvenhQWjVuMllVaGJxdklpU3BONU1Bb0dDQ3FHU000OUJBTURNRWt4Q3pBSkJnTlZCQVlUQWxWVE1ROHdEUVlEVlFRS0RBWkJiV0Y2YjI0eEREQUtCZ05WQkFzTUEwRlhVekViTUJrR0ExVUVBd3dTWVhkekxtNXBkSEp2TFdWdVkyeGhkbVZ6TUI0WERUSTFNRGd5TkRBeE5USTFPVm9YRFRJMU1Ea3hNekF5TlRJMU9Wb3daREVMTUFrR0ExVUVCaE1DVlZNeER6QU5CZ05WQkFvTUJrRnRZWHB2YmpFTU1Bb0dBMVVFQ3d3RFFWZFRNVFl3TkFZRFZRUUREQzB3TUdOaU16TmtNV0ZoT1RZeU5URTNMblZ6TFhkbGMzUXRNaTVoZDNNdWJtbDBjbTh0Wlc1amJHRjJaWE13ZGpBUUJnY3Foa2pPUFFJQkJnVXJnUVFBSWdOaUFBUjJoaXZSOTV6aG9QZVhybG94emVJRE9LaUVkR2ZBOVFKOE9YeU5FZlkzcWZrWWYvOWw0TXJIbnBsS0VsbVFJUUVjN1Y5N28xMlFVVmxEQzZabTVpbjNCSDRqRGV2aDZJRWpIdGxnWE5NQmpJMzhZQVJkM0V3WnNRMWNhYWRRSklTamdkVXdnZEl3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQWpBZkJnTlZIU01FR0RBV2dCU1FKYlVOMlFWSDU1YkRsdnB5bmMrWnFkOUxsakFkQmdOVkhRNEVGZ1FVbWZjTnFOSWx1WWIySTNac0RDbkU4VXlzNnAwd0RnWURWUjBQQVFIL0JBUURBZ0dHTUd3R0ExVWRId1JsTUdNd1lhQmZvRjJHVzJoMGRIQTZMeTloZDNNdGJtbDBjbTh0Wlc1amJHRjJaWE10WTNKc0xuTXpMbUZ0WVhwdmJtRjNjeTVqYjIwdlkzSnNMMkZpTkRrMk1HTmpMVGRrTmpNdE5ESmlaQzA1WlRsbUxUVTVNek00WTJJMk4yWTROQzVqY213d0NnWUlLb1pJemowRUF3TURad0F3WkFJd01kRkR6dFlrR0J5VlRpSCtGL0dHL0srRG9hUXR4MjF5WlV6aDVZeTFkRHdsdzlLblByQ0VqWnVGR0hxUStsbklBakFCcU9SZWptTFVlTFZ2K3hic3ZrQi9ValU2dEJxK2JJakF1U2VVQThiOXc5M1d5ZHU1RmRCQmxWRGFmd3FwYWJCWkF4Z3dnZ01VTUlJQ21xQURBZ0VDQWhBQ0RXVEF4T28rMHZ2dXY1cG02OUt6TUFvR0NDcUdTTTQ5QkFNRE1HUXhDekFKQmdOVkJBWVRBbFZUTVE4d0RRWURWUVFLREFaQmJXRjZiMjR4RERBS0JnTlZCQXNNQTBGWFV6RTJNRFFHQTFVRUF3d3RNREJqWWpNelpERmhZVGsyTWpVeE55NTFjeTEzWlhOMExUSXVZWGR6TG01cGRISnZMV1Z1WTJ4aGRtVnpNQjRYRFRJMU1EZ3lOekUwTWprd04xb1hEVEkxTURrd01qQTBNamt3TjFvd2dZa3hQREE2QmdOVkJBTU1NMlZtWWpRNVpESTBOamhqWlRObU9HRXVlbTl1WVd3dWRYTXRkMlZ6ZEMweUxtRjNjeTV1YVhSeWJ5MWxibU5zWVhabGN6RU1NQW9HQTFVRUN3d0RRVmRUTVE4d0RRWURWUVFLREFaQmJXRjZiMjR4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSlhRVEVRTUE0R0ExVUVCd3dIVTJWaGRIUnNaVEIyTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFpQTJJQUJFR28yS3laa1NNWSttME5VMUpOcjZUS3ZwYW1PN0JWd2dSeW9EZEdGNE9rejl2M1NxZ2FIdUo1djhSOHgydm1zR2ZlYVg3eS96MWd6d0VuaWt1YncwRm5lUjNLektiWjU3MENWK01LK3RwbzJTd2JoUlJmNk1maUlJSjlRVUR2QzZPQjZqQ0I1ekFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VCTUI4R0ExVWRJd1FZTUJhQUZKbjNEYWpTSmJtRzlpTjJiQXdweFBGTXJPcWRNQjBHQTFVZERnUVdCQlErUDlROW5RcU12Z0ovbEJ4Q0tucU5nd0dSNFRBT0JnTlZIUThCQWY4RUJBTUNBWVl3Z1lBR0ExVWRId1I1TUhjd2RhQnpvSEdHYjJoMGRIQTZMeTlqY213dGRYTXRkMlZ6ZEMweUxXRjNjeTF1YVhSeWJ5MWxibU5zWVhabGN5NXpNeTUxY3kxM1pYTjBMVEl1WVcxaGVtOXVZWGR6TG1OdmJTOWpjbXd2Wm1FeE56Y3dORFF0WTJVeE5DMDBNVEV6TFRoak9Ea3RPVEJoWkRFNFpqSmlPR05tTG1OeWJEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQmJEY1hjL29EUnBNdTh0ZVZSWTMrakRIQzZ6SWFxUnc0NmJXVWtiOHV2bk8yaVhZUytBd1VGcG1qcmlremFnb3NDTVFDN3ZZd0dEY25BejEvdHZzZjl6ZHRyZGhVQ2p1cXR6Mnd4UDAwYURxWU1Wd2dnbWVKUGNWblVzRlgzTXBxRGIxOVpBc013Z2dLL01JSUNSS0FEQWdFQ0FoUXdSN0N4VHJDcDh3dFJJeEYzQnNyYWd4eHRGVEFLQmdncWhrak9QUVFEQXpDQmlURThNRG9HQTFVRUF3d3paV1ppTkRsa01qUTJPR05sTTJZNFlTNTZiMjVoYkM1MWN5MTNaWE4wTFRJdVlYZHpMbTVwZEhKdkxXVnVZMnhoZG1Wek1Rd3dDZ1lEVlFRTERBTkJWMU14RHpBTkJnTlZCQW9NQmtGdFlYcHZiakVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFsZEJNUkF3RGdZRFZRUUhEQWRUWldGMGRHeGxNQjRYRFRJMU1EZ3lPREF4TURRME1Wb1hEVEkxTURneU9UQXhNRFEwTVZvd2dZNHhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwWFlYTm9hVzVuZEc5dU1SQXdEZ1lEVlFRSERBZFRaV0YwZEd4bE1ROHdEUVlEVlFRS0RBWkJiV0Y2YjI0eEREQUtCZ05WQkFzTUEwRlhVekU1TURjR0ExVUVBd3d3YVMwd05UUXdOMll6WW1Ka1pHSmtOV0l4TWk1MWN5MTNaWE4wTFRJdVlYZHpMbTVwZEhKdkxXVnVZMnhoZG1Wek1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFSmR2RDNvL29wMEE3a3JkK2tsQ2RlYUZzcytGL0hwdDFvYkxkRjJGODg1bHI2TjlqeWxIazZycEhWaVJ1NlZzUFVOdCszQ2o5TzRJMU1RUDNORUphK1M4WUpEaFVVb0VHODFNeExTb1N0NmZFZjV6R1R1NzBDV1RHSGNIeTBZYlFvMll3WkRBU0JnTlZIUk1CQWY4RUNEQUdBUUgvQWdFQU1BNEdBMVVkRHdFQi93UUVBd0lDQkRBZEJnTlZIUTRFRmdRVStYdVJmeS9nNng3aWQzMHpMNHBoZzBnNmd5RXdId1lEVlIwakJCZ3dGb0FVUGovVVBaMEtqTDRDZjVRY1FpcDZqWU1Ca2VFd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFLNnlMaENEMDVHQk03UDZLYlkzSGdhYmFUYjE4UHp0aWdIUDJWN2pIZVFndXpVeHhrWnNUMlhDTlBDbFR3YUlhQUl4QU0zdmIzdUJmQllCaGZXZXk4T21SS0t6by90c0l6Q045ZlRWZndla3dYczJScUpSUTBnb0FpUFRneUI0WU5NQk1XcHdkV0pzYVdOZmEyVjU5bWwxYzJWeVgyUmhkR0ZZZVhzaVkzVnlkbVZmZEhsd1pTSTZJbkF5TlRack1TSXNJbVJoZEdFaU9pSkNTMHczTVZCQ0x6ZEdjMXBwTWxCQ04zZ3JVbUp0VWxoQlMwcHNSR3BKTURCSmNuUlRRVEkyTlRWb1J6WjRWak5OT0hRNVEwSklZa1pvYmxGcFZsUTBXV2hVVGtGV05UWnZOSFpOYUZoQ2NWVk1iWEZtSzNNOUluMWxibTl1WTJYMi8xaGdiMFVtNWgwY0RtNk1iK3R2a1grTDI1UHlYWCtrOEhtcWFwOVk1eHpSM1FwVHRrVTJpMk4weW8xOFAvUXVrbzBzUG9VQjBLbGtrREd3SnVyL1cxZlVpR2l5T0JVbHdrbm5UY0xYaU9SS3Y0QkZDYVJLcEV5Y1ZnV24yRTR0WjkvTSIsImhFU2hBVGdpb0ZrUklMOXBiVzlrZFd4bFgybGtlQ2RwTFRBMU5EQTNaak5pWW1Sa1ltUTFZakV5TFdWdVl6QXhPVGhsTnpVeU9UUmxOMk5sWkRGbVpHbG5aWE4wWmxOSVFUTTROR2wwYVcxbGMzUmhiWEFiQUFBQm1PNWdlcEJrY0dOeWM3QUFXRENUUDkrL1dvU2UwSHdLZ2dINGdUdUt1WGIzTHBOeUt6NUtLTnUwSFNkQisxZ1dCSVJiWW5Ld1NnK2xxTFdXUjcwQldEQkxUVnMyWWJQdndTa2drQXlBNFNia3puZzhVaTNtd0NvcVcvZXZPaXVUSjdobmR2R0k1TDRjSEVCS0VwMjlwSk1DV0RCK0g2Mzc3Rm1ycWRSZU1FTDlNaTFlWWJQb1NkZkg2aUFJVFpSdnpwZHRTSGM2NXlQQTVYNHRZeEgvTm1lanFMVURXREFjK0ppKzV2NXI0RUQ5d291bExKbndudlg1ejZiMVIyekF3Sy9haldhd2lpdnBvYnhQaXd4UklMQ3BHVmxYV2JRRVdEQ2JiMWJlV05EZk5xWWZpWTZlSUhHdXhNWVpyNzdBSnpmNFFkMHg4aEpVREZOZldXbGpXc1oxMUxCVlJEbGh1MzBGV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUdXREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBSFdEQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFJV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUpXREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBS1dEQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFMV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU1XREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBTldEQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFPV0RBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQVBXREFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCclkyVnlkR2xtYVdOaGRHVlpBbjh3Z2dKN01JSUNBYUFEQWdFQ0FoQUJtT2RTbE9mTzBRQUFBQUJvcjdabE1Bb0dDQ3FHU000OUJBTURNSUdPTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tWMkZ6YUdsdVozUnZiakVRTUE0R0ExVUVCd3dIVTJWaGRIUnNaVEVQTUEwR0ExVUVDZ3dHUVcxaGVtOXVNUXd3Q2dZRFZRUUxEQU5CVjFNeE9UQTNCZ05WQkFNTU1Ha3RNRFUwTURkbU0ySmlaR1JpWkRWaU1USXVkWE10ZDJWemRDMHlMbUYzY3k1dWFYUnlieTFsYm1Oc1lYWmxjekFlRncweU5UQTRNamd3TVRVeU16UmFGdzB5TlRBNE1qZ3dORFV5TXpkYU1JR1RNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1YyRnphR2x1WjNSdmJqRVFNQTRHQTFVRUJ3d0hVMlZoZEhSc1pURVBNQTBHQTFVRUNnd0dRVzFoZW05dU1Rd3dDZ1lEVlFRTERBTkJWMU14UGpBOEJnTlZCQU1NTldrdE1EVTBNRGRtTTJKaVpHUmlaRFZpTVRJdFpXNWpNREU1T0dVM05USTVOR1UzWTJWa01TNTFjeTEzWlhOMExUSXVZWGR6TUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVHNUJUZDJEdmxCb3pSK1llMGtJOXBkUVp1dC9qRXNpMnFtVUFpSnB2MVVLK1VaTjJvTGx2STZHSXVSWEVOTVI0RzJjdk9yaGR6Q0NJMTdoTkNPKzFSYXBDcGN2RzhmYkhsVHA4ektTMzFKWVZRdmtyOWFLR1l2ZUtQdEwxbVI5ZG94MHdHekFNQmdOVkhSTUJBZjhFQWpBQU1Bc0dBMVVkRHdRRUF3SUd3REFLQmdncWhrak9QUVFEQXdOb0FEQmxBakJLaEV6VnY5VzZyTVNIYWFGNFZzWlF5Uit1NjYzck82ZmhhbVB3M1pPSnNpUVl1T0VIUUxwbWU2NDEwdGZyN2kwQ01RQ3lOTDdDamhyUkZNc0JuZnN6MktDTm43NEZiY3JIWTZVc3RDc2t3eEhYaUF6bmtKQ0g5K0JHTGcreVgwRHEzSHRvWTJGaWRXNWtiR1dFV1FJVk1JSUNFVENDQVphZ0F3SUJBZ0lSQVBreGRXZ2JrSy9oSFViTXRPVG4rRll3Q2dZSUtvWkl6ajBFQXdNd1NURUxNQWtHQTFVRUJoTUNWVk14RHpBTkJnTlZCQW9NQmtGdFlYcHZiakVNTUFvR0ExVUVDd3dEUVZkVE1Sc3dHUVlEVlFRRERCSmhkM011Ym1sMGNtOHRaVzVqYkdGMlpYTXdIaGNOTVRreE1ESTRNVE15T0RBMVdoY05ORGt4TURJNE1UUXlPREExV2pCSk1Rc3dDUVlEVlFRR0V3SlZVekVQTUEwR0ExVUVDZ3dHUVcxaGVtOXVNUXd3Q2dZRFZRUUxEQU5CVjFNeEd6QVpCZ05WQkFNTUVtRjNjeTV1YVhSeWJ5MWxibU5zWVhabGN6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQlB3Q1ZPdW1DTUh6YUhEaW10cVF2a1k0TXBKemJvbEwvL1p5MllsRVMxQlI1VFNrc2ZiYjQ4QzhXQm95dDdGMkJ3N2VFdGFhUCtvaEcyYm5Vczk5MGQwSlgyOFRjUFFYQ0VQWjNCQUJJZVRQWXdFb0NXWkVoOGw1WW9Rd1RjVS85S05DTUVBd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWtDVzFEZGtGUitlV3c1YjZjcDNQbWFuZlM1WXdEZ1lEVlIwUEFRSC9CQVFEQWdHR01Bb0dDQ3FHU000OUJBTURBMmtBTUdZQ01RQ2pmeStSb2NtOVh1ZTRZbndXbU5KVkE0NGZBMFA1VzJPcFlvdzlPWUNWUmFFZXZMOHVPMVhZcnU1eHRNUFdyZk1DTVFDaTg1c1dCYkp3S0tYZFM2QnB0UUZ1WmJUNzNvL2dCaDFxVXhsL25OcjEyVU84WWZ3cjZ3UExiKzZOSXdMejMvWlpBc0V3Z2dLOU1JSUNSS0FEQWdFQ0FoQVJvenhQWjVuMllVaGJxdklpU3BONU1Bb0dDQ3FHU000OUJBTURNRWt4Q3pBSkJnTlZCQVlUQWxWVE1ROHdEUVlEVlFRS0RBWkJiV0Y2YjI0eEREQUtCZ05WQkFzTUEwRlhVekViTUJrR0ExVUVBd3dTWVhkekxtNXBkSEp2TFdWdVkyeGhkbVZ6TUI0WERUSTFNRGd5TkRBeE5USTFPVm9YRFRJMU1Ea3hNekF5TlRJMU9Wb3daREVMTUFrR0ExVUVCaE1DVlZNeER6QU5CZ05WQkFvTUJrRnRZWHB2YmpFTU1Bb0dBMVVFQ3d3RFFWZFRNVFl3TkFZRFZRUUREQzB3TUdOaU16TmtNV0ZoT1RZeU5URTNMblZ6TFhkbGMzUXRNaTVoZDNNdWJtbDBjbTh0Wlc1amJHRjJaWE13ZGpBUUJnY3Foa2pPUFFJQkJnVXJnUVFBSWdOaUFBUjJoaXZSOTV6aG9QZVhybG94emVJRE9LaUVkR2ZBOVFKOE9YeU5FZlkzcWZrWWYvOWw0TXJIbnBsS0VsbVFJUUVjN1Y5N28xMlFVVmxEQzZabTVpbjNCSDRqRGV2aDZJRWpIdGxnWE5NQmpJMzhZQVJkM0V3WnNRMWNhYWRRSklTamdkVXdnZEl3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQWpBZkJnTlZIU01FR0RBV2dCU1FKYlVOMlFWSDU1YkRsdnB5bmMrWnFkOUxsakFkQmdOVkhRNEVGZ1FVbWZjTnFOSWx1WWIySTNac0RDbkU4VXlzNnAwd0RnWURWUjBQQVFIL0JBUURBZ0dHTUd3R0ExVWRId1JsTUdNd1lhQmZvRjJHVzJoMGRIQTZMeTloZDNNdGJtbDBjbTh0Wlc1amJHRjJaWE10WTNKc0xuTXpMbUZ0WVhwdmJtRjNjeTVqYjIwdlkzSnNMMkZpTkRrMk1HTmpMVGRrTmpNdE5ESmlaQzA1WlRsbUxUVTVNek00WTJJMk4yWTROQzVqY213d0NnWUlLb1pJemowRUF3TURad0F3WkFJd01kRkR6dFlrR0J5VlRpSCtGL0dHL0srRG9hUXR4MjF5WlV6aDVZeTFkRHdsdzlLblByQ0VqWnVGR0hxUStsbklBakFCcU9SZWptTFVlTFZ2K3hic3ZrQi9ValU2dEJxK2JJakF1U2VVQThiOXc5M1d5ZHU1RmRCQmxWRGFmd3FwYWJCWkF4Z3dnZ01VTUlJQ21xQURBZ0VDQWhBQ0RXVEF4T28rMHZ2dXY1cG02OUt6TUFvR0NDcUdTTTQ5QkFNRE1HUXhDekFKQmdOVkJBWVRBbFZUTVE4d0RRWURWUVFLREFaQmJXRjZiMjR4RERBS0JnTlZCQXNNQTBGWFV6RTJNRFFHQTFVRUF3d3RNREJqWWpNelpERmhZVGsyTWpVeE55NTFjeTEzWlhOMExUSXVZWGR6TG01cGRISnZMV1Z1WTJ4aGRtVnpNQjRYRFRJMU1EZ3lOekUwTWprd04xb1hEVEkxTURrd01qQTBNamt3TjFvd2dZa3hQREE2QmdOVkJBTU1NMlZtWWpRNVpESTBOamhqWlRObU9HRXVlbTl1WVd3dWRYTXRkMlZ6ZEMweUxtRjNjeTV1YVhSeWJ5MWxibU5zWVhabGN6RU1NQW9HQTFVRUN3d0RRVmRUTVE4d0RRWURWUVFLREFaQmJXRjZiMjR4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSlhRVEVRTUE0R0ExVUVCd3dIVTJWaGRIUnNaVEIyTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFpQTJJQUJFR28yS3laa1NNWSttME5VMUpOcjZUS3ZwYW1PN0JWd2dSeW9EZEdGNE9rejl2M1NxZ2FIdUo1djhSOHgydm1zR2ZlYVg3eS96MWd6d0VuaWt1YncwRm5lUjNLektiWjU3MENWK01LK3RwbzJTd2JoUlJmNk1maUlJSjlRVUR2QzZPQjZqQ0I1ekFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VCTUI4R0ExVWRJd1FZTUJhQUZKbjNEYWpTSmJtRzlpTjJiQXdweFBGTXJPcWRNQjBHQTFVZERnUVdCQlErUDlROW5RcU12Z0ovbEJ4Q0tucU5nd0dSNFRBT0JnTlZIUThCQWY4RUJBTUNBWVl3Z1lBR0ExVWRId1I1TUhjd2RhQnpvSEdHYjJoMGRIQTZMeTlqY213dGRYTXRkMlZ6ZEMweUxXRjNjeTF1YVhSeWJ5MWxibU5zWVhabGN5NXpNeTUxY3kxM1pYTjBMVEl1WVcxaGVtOXVZWGR6TG1OdmJTOWpjbXd2Wm1FeE56Y3dORFF0WTJVeE5DMDBNVEV6TFRoak9Ea3RPVEJoWkRFNFpqSmlPR05tTG1OeWJEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQmJEY1hjL29EUnBNdTh0ZVZSWTMrakRIQzZ6SWFxUnc0NmJXVWtiOHV2bk8yaVhZUytBd1VGcG1qcmlremFnb3NDTVFDN3ZZd0dEY25BejEvdHZzZjl6ZHRyZGhVQ2p1cXR6Mnd4UDAwYURxWU1Wd2dnbWVKUGNWblVzRlgzTXBxRGIxOVpBc013Z2dLL01JSUNSS0FEQWdFQ0FoUXdSN0N4VHJDcDh3dFJJeEYzQnNyYWd4eHRGVEFLQmdncWhrak9QUVFEQXpDQmlURThNRG9HQTFVRUF3d3paV1ppTkRsa01qUTJPR05sTTJZNFlTNTZiMjVoYkM1MWN5MTNaWE4wTFRJdVlYZHpMbTVwZEhKdkxXVnVZMnhoZG1Wek1Rd3dDZ1lEVlFRTERBTkJWMU14RHpBTkJnTlZCQW9NQmtGdFlYcHZiakVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFsZEJNUkF3RGdZRFZRUUhEQWRUWldGMGRHeGxNQjRYRFRJMU1EZ3lPREF4TURRME1Wb1hEVEkxTURneU9UQXhNRFEwTVZvd2dZNHhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwWFlYTm9hVzVuZEc5dU1SQXdEZ1lEVlFRSERBZFRaV0YwZEd4bE1ROHdEUVlEVlFRS0RBWkJiV0Y2YjI0eEREQUtCZ05WQkFzTUEwRlhVekU1TURjR0ExVUVBd3d3YVMwd05UUXdOMll6WW1Ka1pHSmtOV0l4TWk1MWN5MTNaWE4wTFRJdVlYZHpMbTVwZEhKdkxXVnVZMnhoZG1Wek1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFSmR2RDNvL29wMEE3a3JkK2tsQ2RlYUZzcytGL0hwdDFvYkxkRjJGODg1bHI2TjlqeWxIazZycEhWaVJ1NlZzUFVOdCszQ2o5TzRJMU1RUDNORUphK1M4WUpEaFVVb0VHODFNeExTb1N0NmZFZjV6R1R1NzBDV1RHSGNIeTBZYlFvMll3WkRBU0JnTlZIUk1CQWY4RUNEQUdBUUgvQWdFQU1BNEdBMVVkRHdFQi93UUVBd0lDQkRBZEJnTlZIUTRFRmdRVStYdVJmeS9nNng3aWQzMHpMNHBoZzBnNmd5RXdId1lEVlIwakJCZ3dGb0FVUGovVVBaMEtqTDRDZjVRY1FpcDZqWU1Ca2VFd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFLNnlMaENEMDVHQk03UDZLYlkzSGdhYmFUYjE4UHp0aWdIUDJWN2pIZVFndXpVeHhrWnNUMlhDTlBDbFR3YUlhQUl4QU0zdmIzdUJmQllCaGZXZXk4T21SS0t6by90c0l6Q045ZlRWZndla3dYczJScUpSUTBnb0FpUFRneUI0WU5NQk1XcHdkV0pzYVdOZmEyVjU5bWwxYzJWeVgyUmhkR0ZZSU9rN25TdVBxbW1Lc1dCN1ZxVGZJSlp5cHdvT3R2VEtrT1huMWdtUjJZeXhaVzV2Ym1ObDl2OVlZTE84d3lkMnpYZ0d3SkUzRVNNREJmWi9HZktYUmNIaWRkQWhGK2VjV2gyb1pnbVNabllWRUlWeC8wRzA1dzhFaVM5TWYzQnpCcVplMzYybmpIUndQNW5ZZUhuSGtFcE1QT3BMaGYwNTFsMEowZUtsVHZrRkllUFlSTDRSNk95L1lRPT0iXX0=","claims":{"enclave_measurement":{"platform":"nitro","code":"933fdfbf5a849ed07c0a8201f8813b8ab976f72e93722b3e4a28dbb41d2741fb581604845b6272b04a0fa5a8b59647bd.4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493.7e1fadfbec59aba9d45e3042fd322d5e61b3e849d7c7ea20084d946fce976d48773ae723c0e57e2d6311ff3667a3a8b5"},"public_key":{"curve_type":"p256k1","data":"BKL71PB/7FsZi2PB7x+RbmRXAKJlDjI00IrtSA2655hG6xV3M8t9CBHbFhnQiVT4YhTNAV56o4vMhXBqULmqf+s="}}},"transitive_attested_function_call":{"transitive_attestation":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA5ZjY0NGU5ODE1ZmQ5NGI0NGEwMzc2NzE4NTYzMzUzMzc2Yjk5ZmVhZmQ4ZmFmZThiYTFmNTllNzQ2ZTA3M2VhMTYzODhhZmI5YmU2MzM1NjYxNThkYzYyYmM0NzJhYjhlYWEzOWU0NGQ0ZTA3MDI3OTdiZTM0YmMwNGY2MWZlYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKaGVsbG9Xb3JsZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgGE2OWY3M2NjYTIzYTlhYzVjOGI1NjdkYzE4NWE3NTZlOTdjOTgyMTY0ZmUyNTg1OWUwZDFkY2MxNDc1YzgwYTYxNWIyMTIzYWYxZjVmOTRjMTFlM2U5NDAyYzNhYzU1OGY1MDAxOTlkOTViNmQzZTMwMTc1ODU4NjI4MWRjZDI2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1IZWxsbywgV29ybGQhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAOTM3NTQ0N2NkNTMwN2JmNzQ3M2I4MjAwZjAzOWI2MGEzYmU0OTEyODJmODUyZGY5ZjQyY2UzMWE4YTQzZjZmOGU5MTZjNGY4MjY0ZTdkMjMzYWRkNDg3NDZhNDAxNjZlZWM1ODhiZThiN2I5YjE2YTVlYjY5OGQ0YzNiMDZlMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQSayx2f2upemMDO3gPTDfyvTzglL5iyx1yrgBspMkjmOHKEOGqNikOddUkeB31ibN5kvp/aoushZ1Ie7CW6x3h8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","claims":{"hash_of_code":"9f644e9815fd94b44a0376718563353376b99feafd8fafe8ba1f59e746e073ea16388afb9be633566158dc62bc472ab8eaa39e44d4e0702797be34bc04f61fec","function":"helloWorld","hash_of_input":"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26","output":"SGVsbG8sIFdvcmxkIQ==","hash_of_secrets":"9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00"}}}
Notice that verified.json
is identical in content to out.json
by running:
diff <(jq -S '.' out.json) <(jq -S '.' verified.json)
When the bky-as verify-fn-call
command succeeds, you know that the points 1-3 above hold true for attestations in verified.json
.
Relating Attestation Claims to Blocky AS Guarantees
Let's put all the pieces together and relate the claims made by the attestations in out.json to the guarantees we defined in the Overview section of this tutorial.
From Step 4, we know that the enclave_attested_application_public_key
attestation was signed by the TEE hardware manufacturer's private key tee_priv_key
, which is the root of trust for TEE enclaves. As a consequence, we know that the enclave_attested_application_public_key
was generated inside a TEE.
From Step 2, we know that the BUILT_CODE
measurement matches the RELEASE_CODE
measurement and from Step 1 we know that RELEASE_CODE
measurement matches the enclave_code.txt
measurement. As a consequence, we know that the enclave_code.txt
measurement matches the BUILT_CODE
measurement and that enclave_attested_application_public_key
was produced by code in delphi/src.tgz
archive.
Combining these facts, we know that the Blocky AS server ran on code in delphi/src.tgz
on a TEE.
From Step 3, we know the Blocky AS server code in delphi/src.tgz
has the following properties:
- Property 1: Blocky AS server generates a unique public/private key pair on startup and retains control of the private key.
- Property 2: Blocky AS server enclave attestations attest the unique public key.
- Property 3: Blocky AS server uses its private key to sign transitive attestations of function calls executed by the Blocky AS server.
- Property 4: Blocky AS executes WASM binaries and faithfully attests the invoked function, its inputs, secrets, output, and hash of the WASM binary.
- Property 5: Blocky AS server does not allow tampering with the intermediate state of the function execution.
- Property 6: Blocky AS server does not leak the intermediate state of the function execution.
- Property 7: Blocky AS server does not leak the secrets passed to the function.
From Step 4 and Properties 1 and 2 we know that enclave_attested_application_public_key
attests the unique public key encl_app_pub_key
of the Blocky AS server. Further, from Property 3 we know that transitive_attested_function_call
is signed by the unique private key encl_app_priv_key
of the Blocky AS server. Finally, from Property 4 we know that transitive_attested_function_call
claims represent the execution of a function in a WASM binary, which was executed by the Blocky AS server inside a TEE enclave.
Ultimately, we can proceed to demonstrate the guarantees of Blocky AS defined in the Overview section of this tutorial:
- Guarantee 1: Attested function output is the result of an execution of a specific function on given inputs and secrets, since
transitive_attested_function_call
contains thehash_of_code
,function
,hash_of_input
,hash_of_secrets
, andoutput
fields, which attest the function execution on a TEE. - Guarantee 2: The intermediate state of the function execution is not tampered with, since the function execution took place inside a TEE and Property 5 guarantees that the Blocky AS server does not allow tampering with the intermediate state of the function execution.
- Guarantee 3: The intermediate state of the function execution is not leaked since the function execution took place inside a TEE and Property 6 guarantees that the Blocky AS server does not leak the intermediate state of the function execution.
- Guarantee 4: The secrets passed to the function are not leaked, since the function execution took place inside a TEE and Property 7 guarantees that the Blocky AS server does not leak the secrets passed to the function.
Summary
OK, that was a lot of detail. Let's zoom out summarize the facts we learned in the verification process.
Simply put, attested function output:
jq -r '.transitive_attested_function_call.claims.output | @base64d' out.json
Hello, World!
was produced by invoking the:
jq -r '.transitive_attested_function_call.claims.function' out.json
helloWorld
function in a WASM binary that hashes to:
jq -r '.transitive_attested_function_call.claims.hash_of_code' out.json
9f644e9815fd94b44a0376718563353376b99feafd8fafe8ba1f59e746e073ea16388afb9be633566158dc62bc472ab8eaa39e44d4e0702797be34bc04f61fec
on input hashing to:
jq -r '.transitive_attested_function_call.claims.hash_of_input' out.json
a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26
and secrets hashing to:
jq -r '.transitive_attested_function_call.claims.hash_of_secrets' out.json
9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00
while ensuring that the intermediate state of the function execution is not tampered with and is not leaked, and that the secrets passed to the function are not leaked, since the function execution took place inside a TEE enclave.
Now you, or anyone else, with out.json
can verify these claims by following the verification process described in this tutorial, and so trust the execution of the attested function call.
Next Steps
You can store Blocky AS attestations to always demonstrate the veracity of the attested function output. You can also verify Blocky AS attestations on chain to use their output to trigger on-chain actions in your smart contracts.