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.