Last updated

Bringing Attestations On Chain

The Blocky Attestation Service attest-fn-call command allows you to submit a function call to a TEE for execution. You can bring these attestations on chain to user their attested data in your smart contracts. Below we walk you through an example of bringing a Blocky AS function call attestation on chain. If you haven't already, you may want to go through the Attesting Function Calls example to learn how to create a function call attestation.

Setup

  • Install npm by following these instructions.

  • Clone our https://github.com/blocky/attestation-service-examples repository and navigate to the hello_world_on_chain directory.

    git clone git@github.com:blocky/attestation-service-examples.git
    cd attestation-service-examples/hello_world_on_chain
  • Set up the project dependencies:

    npm install --dd

Quick Start

To run this example, call:

make test-local

which will test verifying an attested function call in User.sol within a local test environment:

  Local Test
Verified attest-fn-call claims:
        Function: helloWorld
        Hash of code: a8c69ed89187a4a6b46f0e7e3e30e144784fe4f60640a0839f1e7e81af31a4cf0ec82954172094167281a3b8ed3ddb2964fc53cc3c4d03d0ba8cd680db5a9ff6
        Hash of input: a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26
        Hash of secrets: 9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00
        Output: Hello, World!
    ✔ Set signing key and verify TA (675ms)

Walkthrough

Step 1: Attest a function call

In the Attesting a Function Calls example we walked through a process of attesting a WASM function call. The result of that process was a tmp/out.json file containing the enclave attested application public key and a transitive attestation of the function call. The contents of tmp/out.json have been verified by the Blocky AS CLI. To learn more about that process, revisit the Attesting a Function Calls example, or read about Attestations in the Blocky Attestation Service in our documentation.

For this example, we have copied tmp/out.json from the previous example into inputs/out.json. To verify the output of the WASM function call in a smart contract, we will extract from out.json the transitive attestation of function call, as well as the enclave attested application public key of the Blocky AS server. As you'll see later, this example is driven by tests in tests/user.ts. In that file, we use the loadEVMLinkData function to extract the transitive attestation and enclave attested application public key from inputs/out.json. If you like, you can see these values now by running:

jq -r '.function_calls[0].transitive_attestation' inputs/out.json

and

jq -r '.enclave_attested_application_public_key.claims.public_key.data' inputs/out.json

Step 2: Write a smart contract to verify a function call attestation

For this example, we have created user contract in contracts/User.sol. Its goal is to verify that a transitive attestation has been signed by the enclave attested application public key of the Blocky AS server and parse out the attested function output.

The first step in that process is to set the enclave attested application public key, used to sign transitive attestations, in contract memory. We do this by calling the setTASigningKeyAddress function:

    address private taSigningKeyAddress;

    function setTASigningKeyAddress(
        bytes calldata taSigningKey
    )
    public onlyOwner
    {
        taSigningKeyAddress = publicKeyToAddress(taSigningKey);
    }

The setTASigningKeyAddress function uses OpenZeppelin's Ownable contract to allow only the owner (publisher) of the User contract to set transitive attestation signing key in contract memory. This step is important, since the owner is the one who obtained and verified the enclave application public key using the bky-as CLI. For now, users of the User contract have to trust the contract owner to set the correct transitive attestation signing key. In the future, we will make this process fully trustless, by allowing smart contracts to verify enclave attestations directly to extract and set the transitive attestation signing key.

The User contract uses the Blocky-provided lib/TAParserLib.sol library, which offers several utility functions. One of these is publicKeyToAddress function converts the enclave attested application public key to an Ethereum address.

The next step is to verify the transitive attestation. The User contract provides the verifyAttestedFnCallClaims function:

    function verifyAttestedFnCallClaims(
        string calldata taData
    )
    public
    {
        TAParser.FnCallClaims memory claims = parseTA(
            taData,
            taSigningKeyAddress
        );

        console.log("Verified attest-fn-call claims:");
        console.log("\tFunction: %s", claims.Function);
        console.log("\tHash of code: %s", claims.HashOfCode);
        console.log("\tHash of input: %s", claims.HashOfInput);
        console.log("\tHash of secrets: %s", claims.HashOfSecrets);
        console.log("\tOutput: %s", claims.Output);

        emit AttestedFunctionCallOutput(claims.Output);
    }

to verify a transitive attestation passed in as taData. The bulk of that work takes place in TAParserLib.sol parseTA function where we decode taData and check that it is signed by taSigningKeyAddress.

At this point, you may want to extend the User contract verifyAttestedFnCallClaims function to do more than just print the transitive attestation claims to the console. You may want to add additional logic to verify the various claims fields to make sure that the transitive attestation is over the function and inputs expected by your smart contract. You may also take actions based on the output of the function to trigger further smart contract logic.

In our User contract verifyAttestedFnCallClaims function example, we simply print the claims to the console and emit an AttestedFunctionCallOutput event with the Output field of the claims.

Step 3: Test the User contract locally

To test the smart contract locally, we use the Hardhat framework. We define the "Local Test" in test/user.ts that loads inputs/out.json, calls the setTASigningKeyAddress and verifyAttestedFnCallClaims functions on the User contract, and checks that the contract emits the AttestedFunctionCallOutput event with "Hello, World!" as input. You will see the output:

  Local Test
Verified attest-fn-call claims:
        Function: helloWorld
        Hash of code: a8c69ed89187a4a6b46f0e7e3e30e144784fe4f60640a0839f1e7e81af31a4cf0ec82954172094167281a3b8ed3ddb2964fc53cc3c4d03d0ba8cd680db5a9ff6
        Hash of input: a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26
        Hash of secrets: 9375447cd5307bf7473b8200f039b60a3be491282f852df9f42ce31a8a43f6f8e916c4f8264e7d233add48746a40166eec588be8b7b9b16a5eb698d4c3b06e00
        Output: Hello, World!
    ✔ Set signing key and verify TA (675ms)

Step 4: Deploy the Smart Contract to Base Sepolia

You can also deploy the User
contract to Base Sepolia testnet. To do so, we need to do a bit more setup:

  • Create a wallet and fund it with Base Sepolia ETH.
    • If you'd like to user Metamask as your wallet, you can follow this guide to configure it with Base Sepolia.
    • To fund your wallet, you can tap on of the Base Sepolia faucets listed in Base Sepolia documentation.
  • Set your wallet's private key in the .env file under WALLET_KEY.
  • Get a Basescan API key
  • Set your Basescan API key in the .env file under BASESCAN_KEY.

Now you are ready to deploy and test the User contract on Base Sepolia. However, you can skip this step and go directly to Step 5 to test the User contract on Base Sepolia using deployed contract address in the deployments/user_deployed_address file.

To deploy the smart contract, call:

make deploy-base-sepolia

Notice the output of the command similar to:

Successfully verified contract User on the block explorer.
https://sepolia.basescan.org/address/0x41BC1d75EB319573B8e3071e0C5B611eD458e21a#code

which includes a link to the deployed User contract on Basescan.

Step 5: Test the User contract on Base Sepolia

To test the User
contract on Base Sepolia, call:

make test-base-sepolia

which will invoke the "Base Sepolia Tests" in test/user.ts. You will see the test output similar to:

  Base Sepolia Tests
    ✔ Set signing key (5510ms)
    ✔ Verify TA (2174ms)

which show that the User
contract was able to verify the TA in just over 2 seconds. If you go to Basescan to see contract transaction logs you can see that the verifyAttestedFnCallClaims emitted the AttestedFunctionCallOutput event containing the expected "Hello, World!" WASM function output.

Transaction Logs