Last updated

Attesting Function Calls

The Blocky Attestation Service attest-fn-call command allows you to submit a function call to a TEE for execution. Currently, we support functions written in Go and compiled to WebAssembly (WASM) using TinyGo.

A simple price feed oracle

Let's assume you want to implement a simple price feed oracle that fetches a price pair from an API and parses the JSON response. Of course can implement more powerful oracles, for example to access multiple APIs and synthesize their responses through more complex logic than just parsing a JSON response.

In this example, you can get the price of Bitcoin from CoinGecko by calling their ticker API:

curl https://api.coingecko.com/api/v3/coins/bitcoin/tickers | jq .

That gives you a lot of information from multiple markets, but let's say you just want to get the price reported by Binance. The goal is to write a Go function that fetches the ticker data, finds the Binance entry, and parses out the price.

Implementing the oracle

Let's start by setting up a struct to parse the relevant fields from the CoinGecko API reponse JSON:

type CoinGeckoResponse struct {
    Tickers []struct {
        Base   string `json:"base"`
        Market struct {
            Name string `json:"name"`
        } `json:"market"`
        ConvertedLast struct {
            USD float64 `json:"usd"`
        } `json:"converted_last"`
        Timestamp time.Time `json:"timestamp"`
    } `json:"tickers"`
}

Next, we'll define the getPrice function to fetch and parse the data from the CoinGecko API:

func getPrice(market string, coinID string, apiKey string) (Price, error) {
	req := as.HostHTTPRequestInput{
		Method: "GET",
		URL:    fmt.Sprintf("https://api.coingecko.com/api/v3/coins/%s/tickers", coinID),
		Headers: map[string][]string{
			"x-cg-demo-api-key": []string{apiKey},
		},
	}
	resp, err := as.HostFuncHTTPRequest(req)
	if err != nil {
		return Price{}, fmt.Errorf("making http request: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return Price{}, fmt.Errorf(
			"http request failed with status code %d",
			resp.StatusCode,
		)
	}

	coinGeckoResponse := CoinGeckoResponse{}
	err = json.Unmarshal(resp.Body, &coinGeckoResponse)
	if err != nil {
		return Price{}, fmt.Errorf(
			"unmarshaling  data: %w...%s", err,
			resp.Body,
		)
	}

	for _, ticker := range coinGeckoResponse.Tickers {
		if ticker.Market.Name == market {
			return Price{
				Market:    ticker.Market.Name,
				CoinID:    ticker.Base,
				Currency:  "USD",
				Price:     ticker.ConvertedLast.USD,
				Timestamp: ticker.Timestamp,
			}, nil
		}
	}

	return Price{}, fmt.Errorf("market %s not found", market)
}

You will pass in:

  • market: the market name to look for (e.g., "Binance")
  • coinID: the coin ID to look for (e.g., "bitcoin")
  • apiKey: the API key to use for authentication with the CoinGecko API

The as structs and functions will make the HTTP request from the WASM runtime through the Blocky AS server networking stack. The rest of the function is standard Go code.

Next you will define the myOracleFunc function for you to invoke through bky-as:

//export myOracleFunc
func myOracleFunc(inputPtr, secretPtr uint64) uint64 {
	var input Args
	inputData := as.Bytes(inputPtr)
	err := json.Unmarshal(inputData, &input)
	if err != nil {
		outErr := fmt.Errorf("could not unmarshal input args: %w", err)
		return writeErr(outErr.Error())
	}

	var secret SecretArgs
	secretData := as.Bytes(secretPtr)
	err = json.Unmarshal(secretData, &secret)
	if err != nil {
		outErr := fmt.Errorf("could not unmarshal secret args: %w", err)
		return writeErr(outErr.Error())
	}

	price, err := getPrice(input.Market, input.CoinID, secret.CoinGeckoAPIKey)
	if err != nil {
		outErr := fmt.Errorf("getting price: %w", err)
		return writeErr(outErr.Error())
	}

	return writePrice(price)
}

The myOracleFunc accepts your input and secrets (encrypted by bky-as and decrypted by the Blocky AS server in a TEE) and calls the getPrice function. It then returns the results through shared memory to the Blocky AS server to create an attestation to send back to the bky-as CLI. Notice that the myOracleFunc function is marked with //export myOracleFunc, which exports the function to a guest module of the WASM runtime so that we can invoke it on a TEE. You'll also notice the writeErr and writePrice functions that are used to return the results of the function call through the Blocky AS server to the bky-as CLI.

Running the oracle

To run the oracle, check out the https://github.com/blocky/attestation-service-examples repository and navigate to the fetch_and_process_api_call directory.

git clone git@github.com:blocky/attestation-service-examples.git
cd attestation-service-examples/fetch_and_process_api_call

The first step is to build the WASM binary from main.go, which contains the definitions of myOracleFunc, getPrice, and several other helper functions. You can build the binary by running:

make build

which uses TinyGo to compile the Go code to the tmp/x.wasm file.

To send x.wasm to the Blocky AS server you need to define a fn-call.json:

[
  {
    "code_file": "tmp/x.wasm",
    "function": "myOracleFunc",
    "input": {
      "market": "Binance",
      "coin_id": "bitcoin"
    },
    "secret": {
      "api_key": "CoingGecko API key"
    }
  }
]

where:

  • code_file: the path to the compiled WASM binary (in this case, tmp/x.wasm)
  • function: the name of the function to call (in this case, myOracleFunc)
  • input: the input parameters to pass to the function (in this case, the market and coin_id)
  • secret: the secret parameters to pass to the function (in this case, the api_key for CoinGecko, which you can get from their website)

To invoke the oracle call:

make run

which pipes the fn-call.json file to the bky-as CLI and parses the response to give you the attested output written out by myOracleFunc:

{
  "Success": true,
  "Value": {
    "market": "Binance",
    "coin_id": "BTC",
    "currency": "USD",
    "price": 97338,
    "timestamp": "2025-02-14T23:20:35Z"
  },
  "Error": ""
}

where

  • Success: tells you where the function call was successful
  • Value: the value returned by the function call
  • Error: any error that occurred during the function call (if Success is false)