Skip to content

Oracle Program#

The Aleo Oracle program is designed to store information provided by the Attestation Reports generated in TEEs by the Oracle Notarization backend. The program can verify that the report is valid, and it was signed by an allowed key, and it was generated by a backend running a certain version of the software.

This page explains in detail the source code of the Aleo Oracle and how to use it.

Setting up local oracle for testing#

If you want to test your integration locally without interacting with a live network you can use your locally set up network and deploy your own oracle using source code below.

Requirements:

Steps to run local network:

  • Start 4 instances of validator nodes to start mining blocks locally. Open 4 terminals and run this command (1 per terminal):

    snarkos start --nodisplay --dev 0 --validator
    snarkos start --nodisplay --dev 1 --validator
    snarkos start --nodisplay --dev 2 --validator
    snarkos start --nodisplay --dev 3 --validator
    

  • Change the owner address in oracle source code to the address provided by the 1st instance of the validator node when you started it.

  • Deploy oracle contract to the local network:

    snarkos developer deploy official_oracle.aleo --private-key <privateKey> --query "http://0.0.0.0:3030" --path "./" --broadcast "http://0.0.0.0:3030/mainnet/transaction/broadcast" --priority-fee 0
    

    Where privateKey is the key provided when you started the 1st instance of validator node

  • Update oracle with unique_id, pcr_values and public_key's. You can get unique_id and public_key by requesting /info endpoint of the SGX Oracle Notarization backend and pcr_values with public_key from /info of the Nitro Oracle Notarization backend

    snarkos developer execute official_oracle.aleo set_key '<signerPubKey>' '<true|false>' --private-key <privateKey> --query "http://0.0.0.0:3030" --broadcast "http://0.0.0.0:3030/mainnet/transaction/broadcast"
    
    snarkos developer execute official_oracle.aleo set_unique_id '{ chunk_1: <unique_id_1>, chunk_2: <unique_id_2> }' --private-key <privateKey> --query "http://0.0.0.0:3030" --broadcast "http://0.0.0.0:3030/mainnet/transaction/broadcast"
    
    snarkos developer execute official_oracle.aleo set_pcr_values '{ pcr_0_chunk_1: <pcr_0_chunk_1>, pcr_0_chunk_2: <pcr_0_chunk_2> ... pcr_2_chunk_3: <pcr_2_chunk_3> }' --private-key <privateKey> --query "http://0.0.0.0:3030" --broadcast "http://0.0.0.0:3030/mainnet/transaction/broadcast"
    

  • Oracle is set and ready to be used.

Aleo Oracle program#

Mainnet Oracle program#

official_oracle.aleo

You can find this program deployed in the mainnet by visiting the official Aleo Explorer or Aleo123 explorer.

Testnet Oracle program#

official_oracle_v2.aleo

You can find this program deployed in the testnet by visiting the official Aleo Explorer or Aleo123 explorer.

Tip

You may have seen and/or used official_oracle.aleo program in testnet. That program is not supported anymore, and you should use official_oracle_v2.aleo instead. See the migration guide for more information.

Types#

UniqueID#

UniqueID

1
2
3
4
struct UniqueID {
  chunk_1: u128,
  chunk_2: u128
}
1
2
3
struct UniqueID:
  chunk_1 as u128;
  chunk_2 as u128;

UniqueID stores SGX unique ID for assertions on the source code that produced an attestation report.

PcrValues#

PcrValues

  struct PcrValues {
    pcr_0_chunk_1: u128,
    pcr_0_chunk_2: u128,
    pcr_0_chunk_3: u128,
    pcr_1_chunk_1: u128,
    pcr_1_chunk_2: u128,
    pcr_1_chunk_3: u128,
    pcr_2_chunk_1: u128,
    pcr_2_chunk_2: u128,
    pcr_2_chunk_3: u128
  }
struct PcrValues:
    pcr_0_chunk_1 as u128;
    pcr_0_chunk_2 as u128;
    pcr_0_chunk_3 as u128;
    pcr_1_chunk_1 as u128;
    pcr_1_chunk_2 as u128;
    pcr_1_chunk_3 as u128;
    pcr_2_chunk_1 as u128;
    pcr_2_chunk_2 as u128;
    pcr_2_chunk_3 as u128;

PcrValues stores Nitro PCR values for assertions on the source code that produced an attestation report.

AttestedData#

AttestedData

1
2
3
4
struct AttestedData {
  data: u128,
  timestamp: u128
}
1
2
3
struct AttestedData:
  data as u128;
  attestation_timestamp as u128;

AttestedData is the type for storing the attested data in the blockchain. The struct includes an attestation timestamp.

TimestampedHash#

TimestampedHash

1
2
3
4
struct TimestampedHash {
  request_hash: u128,
  attestation_timestamp: u128
}
1
2
3
struct TimestampedHash:
  request_hash as u128;
  attestation_timestamp as u128;

TimestampedHash is a helper type for computing timestamped request hash for storing the timestamped attested data in the blockchain.

PositionData#

TimestampedHash

1
2
3
4
5
6
7
struct PositionData {
  block_index: u8,
  shift_a: u8,
  shift_b: u8,
  mask_a: u128,
  mask_b: u128
}
1
2
3
4
5
6
struct PositionData:
    block_index as u8;
    shift_a as u8;
    shift_b as u8;
    mask_a as u128;
    mask_b as u128;

PositionData provides information on how to extract certain data from the Nitro report such as data hash or PCR values.

Mappings#

sgx_unique_id#

sgx_unique_id

mapping sgx_unique_id: u8 => UniqueID;
1
2
3
mapping sgx_unique_id:
  key as u8.public;
  value as UniqueID.public;

sgx_unique_id mapping stores 32 bytes of SGX enclave's unique ID as a struct of 2 16-byte u128 formatted chunks under the 0u8 key. The program will allow reports only from the backends with this unique ID to make sure that report was produced by a backend running this specific version of source code. You can check which sgx_unique_id Oracle Notarization backend is running by requesting the /info endpoint of an SGX notarizer, which is currently hosted at sgx.aleooracle.xyz.

You can also run the Oracle Notarization backend yourself using reproducible builds and see the generated unique ID. It should match the sgx_unique_id provided by other Oracle Notarization backends if they run the same code.

nitro_pcr_values#

nitro_pcr_values

mapping nitro_pcr_values: u8 => PcrValues;
1
2
3
mapping nitro_pcr_values:
  key as u8.public;
  value as PcrValues.public;

nitro_pcr_values mapping stores bytes of 3 PCR values (PCR_0, PCR_1 and PCR_2) from Nitro enclave as a struct of 9 16-byte u128 formatted chunks (3 chunks per pcr value) under the 0u8 key. The program will allow reports only from the backends with this PCR values to make sure that report was produced by a backend running this specific version of source code. You can check which nitro_pcr_values Oracle Notarization backend is running by requesting the /info endpoint of an Nitro notarizer, which is currently hosted at nitro.aleooracle.xyz.

You can also run the Oracle Notarization backend yourself using reproducible builds and see the generated PCR values. It should match the nitro_pcr_values provided by other Oracle Notarization backends if they run the same code.

allowed_keys#

allowed_keys

mapping allowed_keys: address => bool;
1
2
3
mapping allowed_keys:
  key as address.public;
  value as boolean.public;

allowed_keys contains public keys generated by the Oracle Notarization backends which are used to verify the Schnorr signature of a report. Each time the Oracle Notarization backend starts it generates a key, which is used to create a signature. We store a list of keys from the backends that are verified and trusted, and reject all reports with untrusted keys.

sgx_attested_data#

sgx_attested_data

mapping sgx_attested_data: u128 => AttestedData;
1
2
3
mapping sgx_attested_data:
  key as u128.public;
  value as AttestedData.public;

sgx_attested_data stores the data acquired by an SGX notarizer with a timestamp from a specific request. You can always access the stored data using your Request Hash as a key or Timestamped Request Hash to get data the that was produced at a specific timestamp.

nitro_attested_data#

nitro_attested_data

mapping nitro_attested_data: u128 => AttestedData;
1
2
3
mapping nitro_attested_data:
  key as u128.public;
  value as AttestedData.public;

nitro_attested_data stores the data acquired by a Nitro notarizer with a timestamp from a specific request. You can always access the stored data using your Request Hash as a key or Timestamped Request Hash to get the data that was produced at a specific timestamp.

Transitions#

set_key#

set_key

async transition set_key(
  public pub_key: address,
  public allowed: bool
) -> Future {
  // only owner can change list of allowed keys
  assert_eq(owner, self.caller);

  return finalize_set_key(
    pub_key,
    allowed
  );
}

async function finalize_set_key(
  public pub_key: address,
  public allowed: bool
) {
  Mapping::set(allowed_keys, pub_key, allowed);
}
function set_key:
    input r0 as address.public;
    input r1 as boolean.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_key r0 r1 into r2;
    output r2 as official_oracle.aleo/set_key.future;

finalize set_key:
    input r0 as address.public;
    input r1 as boolean.public;
    set r1 into allowed_keys[r0];

This function is used by the owner of the program to add or remove allowed public keys that are used to verify Schnorr signature. The function sets the "allowed" state to the passed adddress.

set_unique_id#

set_unique_id

async transition set_unique_id(public unique_id: UniqueID) -> Future {
  // only owner can change enclave unique id
  assert_eq(owner, self.caller);

  return finalize_set_unique_id(unique_id);
}

async function finalize_set_unique_id(public unique_id: UniqueID) {
  Mapping::set(sgx_unique_id, 0u8, unique_id);
}
1
2
3
4
5
6
7
8
9
function set_unique_id:
    input r0 as UniqueID.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_unique_id r0 into r1;
    output r1 as official_oracle.aleo/set_unique_id.future;

finalize set_unique_id:
    input r0 as UniqueID.public;
    set r0 into sgx_unique_id[0u8];

This function is used by the owner of the program to replace the old SGX enclave unique_id with a new one. unique_id is used to verify that a report was produced using a specific version of the source code. This function is used when the source code of Oracle Notarization backend is updated to make sure that the oracle program can be updated without re-deploying.

set_pcr_values#

set_pcr_values

  async transition set_pcr_values(public pcr_values: PcrValues) -> Future {
    // only owner can change pcr values
    assert_eq(owner, self.caller);

    return finalize_set_pcr_values(pcr_values);
  }

  async function finalize_set_pcr_values(public pcr_values: PcrValues) {
    Mapping::set(nitro_pcr_values, 0u8, pcr_values);
  }
1
2
3
4
5
6
7
8
9
function set_pcr_values:
    input r0 as PcrValues.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_pcr_values r0 into r1;
    output r1 as official_oracle.aleo/set_pcr_values.future;

finalize set_pcr_values:
    input r0 as PcrValues.public;
    set r0 into nitro_pcr_values[0u8];

This function is used by the owner of the program to replace the old Nitro enclave pcr_values with a new ones. pcr_values is used to verify that a report was produced using a specific version of the source code. This function is used when the source code of Oracle Notarization backend is updated to make sure that the oracle program can be updated using set_data_nitro without re-deploying.

set_data_sgx#

set_data_sgx
async transition set_data_sgx(
  public report_data: ReportData,
  public report: Report,
  public sig: signature,
  public pub_key: address
) -> Future {
  verify_sgx_report(report_data, report, sig, pub_key);

  let request_hash: u128 = get_request_hash(report_data);

  let struct_to_hash: TimestampedHash = TimestampedHash {
    request_hash: request_hash,
    attestation_timestamp: report_data.c0.f3
  };

  let timestamped_hash: u128 = Poseidon8::hash_to_u128(struct_to_hash);

  let attested_data: AttestedData = AttestedData {
    data: report_data.c0.f2,
    attestation_timestamp: report_data.c0.f3
  };

  return finalize_set_data_sgx(
    request_hash,
    timestamped_hash,
    attested_data,
    report.c0.f8,
    report.c0.f9,
    pub_key
  );
}

async function finalize_set_data_sgx(
  public request_hash: u128,
  public timestamped_hash: u128,
  public attested_data: AttestedData,
  public report_unique_id_1: u128,
  public report_unique_id_2: u128,
  public pub_key: address
) {
  let pub_key_allowed: bool = Mapping::get_or_use(allowed_keys, pub_key, false);
  assert(pub_key_allowed);

  // verify unique id from the TEE report
  let unique_id: UniqueID = Mapping::get(sgx_unique_id, 0u8);
  assert_eq(unique_id.chunk_1, report_unique_id_1);
  assert_eq(unique_id.chunk_2, report_unique_id_2);

  Mapping::set(sgx_attested_data, timestamped_hash, attested_data);

  // replace latest data if current data is newer
  let latest_data: AttestedData = Mapping::get_or_use(sgx_attested_data, request_hash, AttestedData { data: 0u128, attestation_timestamp: 0u128 });

  if (attested_data.attestation_timestamp > latest_data.attestation_timestamp) {
    Mapping::set(sgx_attested_data, request_hash, attested_data);
  }
}
function set_data_sgx:
    input r0 as ReportData.public;
    input r1 as Report.public;
    input r2 as signature.public;
    input r3 as address.public;
    call verify_sgx_report r0 r1 r2 r3;
    call get_request_hash r0 into r4;
    cast r4 r0.c0.f3 into r5 as TimestampedHash;
    hash.psd8 r5 into r6 as u128;
    cast r0.c0.f2 r0.c0.f3 into r7 as AttestedData;
    async set_data_sgx r4 r6 r7 r1.c0.f8 r1.c0.f9 r3 into r8;
    output r8 as official_oracle.aleo/set_data_sgx.future;

finalize set_data_sgx:
    input r0 as u128.public;
    input r1 as u128.public;
    input r2 as AttestedData.public;
    input r3 as u128.public;
    input r4 as u128.public;
    input r5 as address.public;
    get.or_use allowed_keys[r5] false into r6;
    assert.eq r6 true;
    get sgx_unique_id[0u8] into r7;
    assert.eq r7.chunk_1 r3;
    assert.eq r7.chunk_2 r4;
    set r2 into sgx_attested_data[r1];
    cast 0u128 0u128 into r8 as AttestedData;
    get.or_use sgx_attested_data[r0] r8 into r9;
    gt r2.attestation_timestamp r9.attestation_timestamp into r10;
    branch.eq r10 false to end_then_0_0;
    set r2 into sgx_attested_data[r0];
    branch.eq true true to end_otherwise_0_1;
    position end_then_0_0;
    position end_otherwise_0_1;

This function is used to update the Oracle program with the new attested data from an SGX notarizer. It takes the report_data that was included in the report, the report itself, the Schnorr signature of the provided report and the public_key to verify this signature.

As a first step oracle verifies the report (see verify_sgx_report) and its signature, then it creates the request_hash (see get_request_hash). This hash is going to be used as a key to store the provided attested_data as the latest data produced by the attestation request. Then it computes a timestamped request hash using the TimestampedHash helper type and the Poseidon8 hash. That hash is going to be used as a key to store the provided attested_data as a historical data that is not going to be overwritten. You can read more about how the Request Hash works here.

In the finalize step the Oracle verifies that the attestation report was signed by one of the approved keys, and that the uniquie_id of the report is correct. Then it saves the attested_data using a timestamped request hash as a key. Then it checks that the timestamp of the report creation is newer than the time of the last saved report to make sure the data is not outdated. And then it saves the attested_data using the static request hash as a key.

set_data_nitro#

set_data_nitro
async transition set_data_nitro(
  public report_data: ReportData,
  public report: Report,
  public sig: signature,
  public pub_key: address,
  public data_position: PositionData,
  public pcr_0_position: PositionData,
  public pcr_1_position: PositionData,
  public pcr_2_position: PositionData
) -> Future {
  let hash_blocks: (u128, u128, u128, u128) = select_chunk(report.c8, data_position.block_index);
  let data_hash_from_report: u128 = extract_value(hash_blocks.0, hash_blocks.1, data_position);

  verify_nitro_report(report_data, report, sig, pub_key, data_hash_from_report);

  let request_hash: u128 = get_request_hash(report_data);

  let struct_to_hash: TimestampedHash = TimestampedHash {
    request_hash: request_hash,
    attestation_timestamp: report_data.c0.f3
  };

  let timestamped_hash: u128 = Poseidon8::hash_to_u128(struct_to_hash);

  let attested_data: AttestedData = AttestedData {
    data: report_data.c0.f2,
    attestation_timestamp: report_data.c0.f3
  };

  let pcr_0_block: (u128, u128, u128, u128) = select_chunk(report.c0, pcr_0_position.block_index);
  let pcr_0_chunk_1: u128 = extract_value(pcr_0_block.0, pcr_0_block.1, pcr_0_position);
  let pcr_0_chunk_2: u128 = extract_value(pcr_0_block.1, pcr_0_block.2, pcr_0_position);
  let pcr_0_chunk_3: u128 = extract_value(pcr_0_block.2, pcr_0_block.3, pcr_0_position);

  let pcr_1_block: (u128, u128, u128, u128) = select_chunk(report.c0, pcr_1_position.block_index);
  let pcr_1_chunk_1: u128 = extract_value(pcr_1_block.0, pcr_1_block.1, pcr_1_position);
  let pcr_1_chunk_2: u128 = extract_value(pcr_1_block.1, pcr_1_block.2, pcr_1_position);
  let pcr_1_chunk_3: u128 = extract_value(pcr_1_block.2, pcr_1_block.3, pcr_1_position);

  let pcr_2_block: (u128, u128, u128, u128) = select_chunk(report.c0, pcr_2_position.block_index);
  let pcr_2_chunk_1: u128 = extract_value(pcr_2_block.0, pcr_2_block.1, pcr_2_position);
  let pcr_2_chunk_2: u128 = extract_value(pcr_2_block.1, pcr_2_block.2, pcr_2_position);
  let pcr_2_chunk_3: u128 = extract_value(pcr_2_block.2, pcr_2_block.3, pcr_2_position);

  let pcr_values: PcrValues = PcrValues {
    pcr_0_chunk_1: pcr_0_chunk_1,
    pcr_0_chunk_2: pcr_0_chunk_2,
    pcr_0_chunk_3: pcr_0_chunk_3,
    pcr_1_chunk_1: pcr_1_chunk_1,
    pcr_1_chunk_2: pcr_1_chunk_2,
    pcr_1_chunk_3: pcr_1_chunk_3,
    pcr_2_chunk_1: pcr_2_chunk_1,
    pcr_2_chunk_2: pcr_2_chunk_2,
    pcr_2_chunk_3: pcr_2_chunk_3
  };

  return finalize_set_data_nitro(
    request_hash,
    timestamped_hash,
    attested_data,
    pcr_values,
    pub_key
  );
}

async function finalize_set_data_nitro(
  public request_hash: u128,
  public timestamped_hash: u128,
  public attested_data: AttestedData,
  public report_pcr_values: PcrValues,
  public pub_key: address
) {
  let pub_key_allowed: bool = Mapping::get_or_use(allowed_keys, pub_key, false);
  assert(pub_key_allowed);

  let pcr_values: PcrValues = Mapping::get(nitro_pcr_values, 0u8);
  assert_eq(pcr_values, report_pcr_values);

  Mapping::set(nitro_attested_data, timestamped_hash, attested_data);

  // replace latest data if current data is newer
  let latest_data: AttestedData = Mapping::get_or_use(nitro_attested_data, request_hash, AttestedData { data: 0u128, attestation_timestamp: 0u128 });

  if (attested_data.attestation_timestamp > latest_data.attestation_timestamp) {
    Mapping::set(nitro_attested_data, request_hash, attested_data);
  }
}
function set_data_nitro:
    input r0 as ReportData.public;
    input r1 as Report.public;
    input r2 as signature.public;
    input r3 as address.public;
    input r4 as PositionData.public;
    input r5 as PositionData.public;
    input r6 as PositionData.public;
    input r7 as PositionData.public;
    call select_chunk r1.c8 r4.block_index into r8 r9 r10 r11;
    call extract_value r8 r9 r4 into r12;
    call verify_nitro_report r0 r1 r2 r3 r12;
    call get_request_hash r0 into r13;
    cast r13 r0.c0.f3 into r14 as TimestampedHash;
    hash.psd8 r14 into r15 as u128;
    cast r0.c0.f2 r0.c0.f3 into r16 as AttestedData;
    call select_chunk r1.c0 r5.block_index into r17 r18 r19 r20;
    call extract_value r17 r18 r5 into r21;
    call extract_value r18 r19 r5 into r22;
    call extract_value r19 r20 r5 into r23;
    call select_chunk r1.c0 r6.block_index into r24 r25 r26 r27;
    call extract_value r24 r25 r6 into r28;
    call extract_value r25 r26 r6 into r29;
    call extract_value r26 r27 r6 into r30;
    call select_chunk r1.c0 r7.block_index into r31 r32 r33 r34;
    call extract_value r31 r32 r7 into r35;
    call extract_value r32 r33 r7 into r36;
    call extract_value r33 r34 r7 into r37;
    cast r21 r22 r23 r28 r29 r30 r35 r36 r37 into r38 as PcrValues;
    async set_data_nitro r13 r15 r16 r38 r3 into r39;
    output r39 as official_oracle.aleo/set_data_nitro.future;

finalize set_data_nitro:
    input r0 as u128.public;
    input r1 as u128.public;
    input r2 as AttestedData.public;
    input r3 as PcrValues.public;
    input r4 as address.public;
    get.or_use allowed_keys[r4] false into r5;
    assert.eq r5 true;
    get nitro_pcr_values[0u8] into r6;
    assert.eq r6 r3;
    set r2 into nitro_attested_data[r1];
    cast 0u128 0u128 into r7 as AttestedData;
    get.or_use nitro_attested_data[r0] r7 into r8;
    gt r2.attestation_timestamp r8.attestation_timestamp into r9;
    branch.eq r9 false to end_then_0_2;
    set r2 into nitro_attested_data[r0];
    branch.eq true true to end_otherwise_0_3;
    position end_then_0_2;
    position end_otherwise_0_3;

This function is used to update the Oracle program with the new attested data from a Nitro notarizer. It takes the report_data that was included in the report, the report itself, the Schnorr signature of the provided report, the public_key to verify this signature, the data_position to extract the data hash from the report, pcr_0_position, pcr_1_position and pcr_2_position to extract the PCR values to verify that a report was produced using a specific version of the source code. As a first step oracle extracts the data hash from the report and then verifies the report itself (see verify_nitro_report) and its signature. Then it creates the request_hash (see get_request_hash). This hash is going to be used as the key to store the provided attested_data as the latest data produced by the attestation request. Then it computes a timestamped request hash using the TimestampedHash helper type and the Poseidon8 hash. That hash is going to be used as a key to store the provided attested_data as a historical data that is not going to be overwritten. You can read more about how the Request Hash and the Timestamped Request Hash works here. Then it extracts the pcr_values from the report to verify them later.

In the finalize step the Oracle verifies that the attestation report was signed by one of the approved keys, and that the pcr_values of the report is correct. Then it saves the attested_data using a timestamped request hash as a key. Then it checks that the timestamp of the report creation is newer than the time of the last saved report to make sure the data is not outdated. And then it saves the attested_data using the static request hash as a key.

Functions#

verify_sgx_report#

verify_sgx_report

function verify_sgx_report(
  report_data: ReportData,
  report: Report,
  sig: signature,
  pub_key: address
) {
  // https://github.com/openenclave/openenclave/blob/e9a0423e3a0b242bccbe0b5b576e88b640f88f85/include/openenclave/bits/sgx/sgxtypes.h#L1088
  // verify enclave flags
  // chunk 0 field 7 contains enclave flags
  let enclave_flags: u128 = report.c0.f7;
  assert_eq(enclave_flags & 1u128, 1u128); // enclave initted
  assert_eq(enclave_flags & 2u128, 0u128); // enclave is not in debug mode
  assert_eq(enclave_flags & 4u128, 4u128); // enclave is in 64-bit mode

  let data_hash: u128 = Poseidon8::hash_to_u128(report_data);

  // verify that the hash of the data signed by TEE and is included in the report
  assert_eq(data_hash, report.c0.f24);
  assert_eq(0u128, report.c0.f25);
  assert_eq(0u128, report.c0.f26);
  assert_eq(0u128, report.c0.f27);

  let report_hash: u128 = Poseidon8::hash_to_u128(report);

  // verify that the report was signed by TEE
  let is_valid: bool = signature::verify(sig, pub_key, report_hash);
  assert(is_valid);
}
closure verify_sgx_report:
    input r0 as ReportData;
    input r1 as Report;
    input r2 as signature;
    input r3 as address;
    and r1.c0.f7 1u128 into r4;
    assert.eq r4 1u128;
    and r1.c0.f7 2u128 into r5;
    assert.eq r5 0u128;
    and r1.c0.f7 4u128 into r6;
    assert.eq r6 4u128;
    hash.psd8 r0 into r7 as u128;
    assert.eq r7 r1.c0.f24;
    assert.eq 0u128 r1.c0.f25;
    assert.eq 0u128 r1.c0.f26;
    assert.eq 0u128 r1.c0.f27;
    hash.psd8 r1 into r8 as u128;
    sign.verify r2 r3 r8 into r9;
    assert.eq r9 true;

This function is used to verify that the provided report is valid. As a first step, it takes Poseidon8 hash of data, which is the report data that was included into the report. Then it checks that certain enclave flags are set correctly, for example that the SGX enclave is not in a debug mode (see here for more about SGX flags). Then it checks that the Poseidon8 data_hash that we created is matching the one included in the report. For verification that the report is valid it takes the Poseidon8 hash of the report itself and checks the provided signature on top of the report hash against the provided public_key generated by the enclave (we are going to check that this public key is allowed later in the finalize step of set_data).

Here is a detailed explanation of report fields that are being verified.

extract_value#

extract_value

function extract_value(
  a: u128,
  b: u128,
  position_info: PositionData
) -> u128 {
  let a_masked: u128 = a & position_info.mask_a;
  let a_masked_shifted: u128 = a_masked.shr_wrapped(position_info.shift_a);

  let b_masked: u128 = b & position_info.mask_b;
  let b_masked_shifted: u128 = b_masked.shl_wrapped(position_info.shift_b);

  let result: u128 = a_masked_shifted | b_masked_shifted;

  return result;
}
closure extract_value:
    input r0 as u128;
    input r1 as u128;
    input r2 as PositionData;
    and r0 r2.mask_a into r3;
    shr.w r3 r2.shift_a into r4;
    and r1 r2.mask_b into r5;
    shl.w r5 r2.shift_b into r6;
    or r4 r6 into r7;
    output r7 as u128;

This function is used for a Nitro reports to extract u128 data which is present in a 2 u128 chunks. Since Nitro report is not alligned perfectly with u128 structure, some of the required values might be present in between the 2 u128 chunks. In order to use them for verification we need to extract only the part that we need with bitwise operations. position_data contains information on which parts to extract from the first and second chunks and then we combine extracted parts into the one u128.

This function is used for report_data_hash as well as for pcr_values extraction.

You can read more about data extraction from Nitro reports here.

select_chunk#

select_chunk

function select_chunk(c: DataChunk, pos: u8) -> (u128, u128, u128, u128) {
  if pos == 0u8 {
    return (c.f0, c.f1, c.f2, c.f3);
  }
  if pos == 1u8 {
    return (c.f1, c.f2, c.f3, c.f4);
  }
  if pos == 2u8 {
    return (c.f2, c.f3, c.f4, c.f5);
  }
  if pos == 3u8 {
    return (c.f3, c.f4, c.f5, c.f6);
  }
  if pos == 4u8 {
    return (c.f4, c.f5, c.f6, c.f7);
  }
  if pos == 5u8 {
    return (c.f5, c.f6, c.f7, c.f8);
  }
  if pos == 6u8 {
    return (c.f6, c.f7, c.f8, c.f9);
  }
  if pos == 7u8 {
    return (c.f7, c.f8, c.f9, c.f10);
  }
  if pos == 8u8 {
    return (c.f8, c.f9, c.f10, c.f11);
  }
  if pos == 9u8 {
    return (c.f9, c.f10, c.f11, c.f12);
  }
  if pos == 10u8 {
    return (c.f10, c.f11, c.f12, c.f13);
  }
  if pos == 11u8 {
    return (c.f11, c.f12, c.f13, c.f14);
  }
  if pos == 12u8 {
    return (c.f12, c.f13, c.f14, c.f15);
  }
  if pos == 13u8 {
    return (c.f13, c.f14, c.f15, c.f16);
  }
  if pos == 14u8 {
    return (c.f14, c.f15, c.f16, c.f17);
  }
  if pos == 15u8 {
    return (c.f15, c.f16, c.f17, c.f18);
  }
  if pos == 16u8 {
    return (c.f16, c.f17, c.f18, c.f19);
  }
  if pos == 17u8 {
    return (c.f17, c.f18, c.f19, c.f20);
  }
  if pos == 18u8 {
    return (c.f18, c.f19, c.f20, c.f21);
  }
  if pos == 19u8 {
    return (c.f19, c.f20, c.f21, c.f22);
  }
  if pos == 20u8 {
    return (c.f20, c.f21, c.f22, c.f23);
  }
  if pos == 21u8 {
    return (c.f21, c.f22, c.f23, c.f24);
  }
  if pos == 22u8 {
    return (c.f22, c.f23, c.f24, c.f25);
  }
  if pos == 23u8 {
    return (c.f23, c.f24, c.f25, c.f26);
  }
  if pos == 24u8 {
    return (c.f24, c.f25, c.f26, c.f27);
  }
  if pos == 25u8 {
    return (c.f25, c.f26, c.f27, c.f28);
  }
  if pos == 26u8 {
    return (c.f26, c.f27, c.f28, c.f29);
  }
  if pos == 27u8 {
    return (c.f27, c.f28, c.f29, c.f30);
  }
  if pos == 28u8 {
    return (c.f28, c.f29, c.f30, c.f31);
  }
  if pos == 29u8 {
    return (c.f29, c.f30, c.f31, 0u128);
  }
  if pos == 30u8 {
    return (c.f30, c.f31, 0u128, 0u128);
  }

  return (0u128, 0u128, 0u128, 0u128);
}
closure select_chunk:
    input r0 as DataChunk;
    input r1 as u8;
    is.eq r1 0u8 into r2;
    is.eq r1 1u8 into r3;
    is.eq r1 2u8 into r4;
    is.eq r1 3u8 into r5;
    is.eq r1 4u8 into r6;
    is.eq r1 5u8 into r7;
    is.eq r1 6u8 into r8;
    is.eq r1 7u8 into r9;
    is.eq r1 8u8 into r10;
    is.eq r1 9u8 into r11;
    is.eq r1 10u8 into r12;
    is.eq r1 11u8 into r13;
    is.eq r1 12u8 into r14;
    is.eq r1 13u8 into r15;
    is.eq r1 14u8 into r16;
    is.eq r1 15u8 into r17;
    is.eq r1 16u8 into r18;
    is.eq r1 17u8 into r19;
    is.eq r1 18u8 into r20;
    is.eq r1 19u8 into r21;
    is.eq r1 20u8 into r22;
    is.eq r1 21u8 into r23;
    is.eq r1 22u8 into r24;
    is.eq r1 23u8 into r25;
    is.eq r1 24u8 into r26;
    is.eq r1 25u8 into r27;
    is.eq r1 26u8 into r28;
    is.eq r1 27u8 into r29;
    is.eq r1 28u8 into r30;
    is.eq r1 29u8 into r31;
    is.eq r1 30u8 into r32;
    ternary r32 r0.f30 0u128 into r33;
    ternary r32 r0.f31 0u128 into r34;
    ternary r32 0u128 0u128 into r35;
    ternary r32 0u128 0u128 into r36;
    ternary r31 r0.f29 r33 into r37;
    ternary r31 r0.f30 r34 into r38;
    ternary r31 r0.f31 r35 into r39;
    ternary r31 0u128 r36 into r40;
    ternary r30 r0.f28 r37 into r41;
    ternary r30 r0.f29 r38 into r42;
    ternary r30 r0.f30 r39 into r43;
    ternary r30 r0.f31 r40 into r44;
    ternary r29 r0.f27 r41 into r45;
    ternary r29 r0.f28 r42 into r46;
    ternary r29 r0.f29 r43 into r47;
    ternary r29 r0.f30 r44 into r48;
    ternary r28 r0.f26 r45 into r49;
    ternary r28 r0.f27 r46 into r50;
    ternary r28 r0.f28 r47 into r51;
    ternary r28 r0.f29 r48 into r52;
    ternary r27 r0.f25 r49 into r53;
    ternary r27 r0.f26 r50 into r54;
    ternary r27 r0.f27 r51 into r55;
    ternary r27 r0.f28 r52 into r56;
    ternary r26 r0.f24 r53 into r57;
    ternary r26 r0.f25 r54 into r58;
    ternary r26 r0.f26 r55 into r59;
    ternary r26 r0.f27 r56 into r60;
    ternary r25 r0.f23 r57 into r61;
    ternary r25 r0.f24 r58 into r62;
    ternary r25 r0.f25 r59 into r63;
    ternary r25 r0.f26 r60 into r64;
    ternary r24 r0.f22 r61 into r65;
    ternary r24 r0.f23 r62 into r66;
    ternary r24 r0.f24 r63 into r67;
    ternary r24 r0.f25 r64 into r68;
    ternary r23 r0.f21 r65 into r69;
    ternary r23 r0.f22 r66 into r70;
    ternary r23 r0.f23 r67 into r71;
    ternary r23 r0.f24 r68 into r72;
    ternary r22 r0.f20 r69 into r73;
    ternary r22 r0.f21 r70 into r74;
    ternary r22 r0.f22 r71 into r75;
    ternary r22 r0.f23 r72 into r76;
    ternary r21 r0.f19 r73 into r77;
    ternary r21 r0.f20 r74 into r78;
    ternary r21 r0.f21 r75 into r79;
    ternary r21 r0.f22 r76 into r80;
    ternary r20 r0.f18 r77 into r81;
    ternary r20 r0.f19 r78 into r82;
    ternary r20 r0.f20 r79 into r83;
    ternary r20 r0.f21 r80 into r84;
    ternary r19 r0.f17 r81 into r85;
    ternary r19 r0.f18 r82 into r86;
    ternary r19 r0.f19 r83 into r87;
    ternary r19 r0.f20 r84 into r88;
    ternary r18 r0.f16 r85 into r89;
    ternary r18 r0.f17 r86 into r90;
    ternary r18 r0.f18 r87 into r91;
    ternary r18 r0.f19 r88 into r92;
    ternary r17 r0.f15 r89 into r93;
    ternary r17 r0.f16 r90 into r94;
    ternary r17 r0.f17 r91 into r95;
    ternary r17 r0.f18 r92 into r96;
    ternary r16 r0.f14 r93 into r97;
    ternary r16 r0.f15 r94 into r98;
    ternary r16 r0.f16 r95 into r99;
    ternary r16 r0.f17 r96 into r100;
    ternary r15 r0.f13 r97 into r101;
    ternary r15 r0.f14 r98 into r102;
    ternary r15 r0.f15 r99 into r103;
    ternary r15 r0.f16 r100 into r104;
    ternary r14 r0.f12 r101 into r105;
    ternary r14 r0.f13 r102 into r106;
    ternary r14 r0.f14 r103 into r107;
    ternary r14 r0.f15 r104 into r108;
    ternary r13 r0.f11 r105 into r109;
    ternary r13 r0.f12 r106 into r110;
    ternary r13 r0.f13 r107 into r111;
    ternary r13 r0.f14 r108 into r112;
    ternary r12 r0.f10 r109 into r113;
    ternary r12 r0.f11 r110 into r114;
    ternary r12 r0.f12 r111 into r115;
    ternary r12 r0.f13 r112 into r116;
    ternary r11 r0.f9 r113 into r117;
    ternary r11 r0.f10 r114 into r118;
    ternary r11 r0.f11 r115 into r119;
    ternary r11 r0.f12 r116 into r120;
    ternary r10 r0.f8 r117 into r121;
    ternary r10 r0.f9 r118 into r122;
    ternary r10 r0.f10 r119 into r123;
    ternary r10 r0.f11 r120 into r124;
    ternary r9 r0.f7 r121 into r125;
    ternary r9 r0.f8 r122 into r126;
    ternary r9 r0.f9 r123 into r127;
    ternary r9 r0.f10 r124 into r128;
    ternary r8 r0.f6 r125 into r129;
    ternary r8 r0.f7 r126 into r130;
    ternary r8 r0.f8 r127 into r131;
    ternary r8 r0.f9 r128 into r132;
    ternary r7 r0.f5 r129 into r133;
    ternary r7 r0.f6 r130 into r134;
    ternary r7 r0.f7 r131 into r135;
    ternary r7 r0.f8 r132 into r136;
    ternary r6 r0.f4 r133 into r137;
    ternary r6 r0.f5 r134 into r138;
    ternary r6 r0.f6 r135 into r139;
    ternary r6 r0.f7 r136 into r140;
    ternary r5 r0.f3 r137 into r141;
    ternary r5 r0.f4 r138 into r142;
    ternary r5 r0.f5 r139 into r143;
    ternary r5 r0.f6 r140 into r144;
    ternary r4 r0.f2 r141 into r145;
    ternary r4 r0.f3 r142 into r146;
    ternary r4 r0.f4 r143 into r147;
    ternary r4 r0.f5 r144 into r148;
    ternary r3 r0.f1 r145 into r149;
    ternary r3 r0.f2 r146 into r150;
    ternary r3 r0.f3 r147 into r151;
    ternary r3 r0.f4 r148 into r152;
    ternary r2 r0.f0 r149 into r153;
    ternary r2 r0.f1 r150 into r154;
    ternary r2 r0.f2 r151 into r155;
    ternary r2 r0.f3 r152 into r156;
    output r153 as u128;
    output r154 as u128;
    output r155 as u128;
    output r156 as u128;

This is a workaround function for Aleo not having dynamic array/struct access. It is used to select chunks of data from the report to later use in extract_value to get required information from the Nitro report. Function takes an index of a chunk and returns 4 u128 chunks starting from the index.

You can read more about data extraction from Nitro reports here.

verify_nitro_report#

verify_nitro_report

function verify_nitro_report(
  report_data: ReportData,
  report: Report,
  sig: signature,
  pub_key: address,
  hash_from_report: u128
) {
  let report_data_hash: u128 = Poseidon8::hash_to_u128(report_data);
  assert_eq(report_data_hash, hash_from_report);

  let report_hash: u128 = Poseidon8::hash_to_u128(report);

  // verify that the report was signed by TEE
  let is_valid: bool = signature::verify(sig, pub_key, report_hash);
  assert(is_valid);
}
closure verify_nitro_report:
    input r0 as ReportData;
    input r1 as Report;
    input r2 as signature;
    input r3 as address;
    input r4 as u128;
    hash.psd8 r0 into r5 as u128;
    assert.eq r5 r4;
    hash.psd8 r1 into r6 as u128;
    sign.verify r2 r3 r6 into r7;
    assert.eq r7 true;

This function is used to verify that the provided report is valid. As a first step, it takes Poseidon8 hash of data, which is the report data that was included into the report. Then it checks that the Poseidon8 data_hash that we created is matching the one that was extracted from the report. For verification that the report is valid it takes the Poseidon8 hash of the report itself and checks the provided signature on top of the report hash against the provided public_key generated by the enclave (we are going to check that this public key is allowed later in the finalize step of set_data).

Here is a detailed explanation of report fields that are being verified.

get_request_hash#

get_request_hash

function get_request_hash(report_data: ReportData) -> u128 {
  let first_data_chunk: DataChunk = DataChunk {
    f0: report_data.c0.f0,
    f1: report_data.c0.f1,
    f2: 0u128,
    f3: 0u128,
    f4: report_data.c0.f4,
    f5: report_data.c0.f5,
    f6: report_data.c0.f6,
    f7: report_data.c0.f7,
    f8: report_data.c0.f8,
    f9: report_data.c0.f9,
    f10: report_data.c0.f10,
    f11: report_data.c0.f11,
    f12: report_data.c0.f12,
    f13: report_data.c0.f13,
    f14: report_data.c0.f14,
    f15: report_data.c0.f15,
    f16: report_data.c0.f16,
    f17: report_data.c0.f17,
    f18: report_data.c0.f18,
    f19: report_data.c0.f19,
    f20: report_data.c0.f20,
    f21: report_data.c0.f21,
    f22: report_data.c0.f22,
    f23: report_data.c0.f23,
    f24: report_data.c0.f24,
    f25: report_data.c0.f25,
    f26: report_data.c0.f26,
    f27: report_data.c0.f27,
    f28: report_data.c0.f28,
    f29: report_data.c0.f29,
    f30: report_data.c0.f30,
    f31: report_data.c0.f31
  };

  let request_data: ReportData = ReportData {
    c0: first_data_chunk,
    c1: report_data.c1,
    c2: report_data.c2,
    c3: report_data.c3,
    c4: report_data.c4,
    c5: report_data.c5,
    c6: report_data.c6,
    c7: report_data.c7
  };

  let request_hash: u128 = Poseidon8::hash_to_u128(request_data);

  return request_hash;
}
1
2
3
4
5
6
closure get_request_hash:
    input r0 as ReportData;
    cast r0.c0.f0 r0.c0.f1 0u128 0u128 r0.c0.f4 r0.c0.f5 r0.c0.f6 r0.c0.f7 r0.c0.f8 r0.c0.f9 r0.c0.f10 r0.c0.f11 r0.c0.f12 r0.c0.f13 r0.c0.f14 r0.c0.f15 r0.c0.f16 r0.c0.f17 r0.c0.f18 r0.c0.f19 r0.c0.f20 r0.c0.f21 r0.c0.f22 r0.c0.f23 r0.c0.f24 r0.c0.f25 r0.c0.f26 r0.c0.f27 r0.c0.f28 r0.c0.f29 r0.c0.f30 r0.c0.f31 into r1 as DataChunk;
    cast r1 r0.c1 r0.c2 r0.c3 r0.c4 r0.c5 r0.c6 r0.c7 into r2 as ReportData;
    hash.psd8 r2 into r3 as u128;
    output r3 as u128;

get_request_hash computes a hash of the report data without the variable properties like the attestation data and attestation timestamp, as described in the Request Hash guide.

Full source code#

Here is the source code of the Aleo Oracle in both Leo and compiled to Aleo versions:

Aleo Oracle program
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
program official_oracle.aleo {
  // 32 byte enclave unique_id
  // split into 2 16 byte u128 chunks
  struct UniqueID {
    chunk_1: u128,
    chunk_2: u128
  }

  // 3 AWS Nitro PCR_values
  // 48 bytes each, split into 3 16 byte u128 chunks
  struct PcrValues {
    pcr_0_chunk_1: u128,
    pcr_0_chunk_2: u128,
    pcr_0_chunk_3: u128,
    pcr_1_chunk_1: u128,
    pcr_1_chunk_2: u128,
    pcr_1_chunk_3: u128,
    pcr_2_chunk_1: u128,
    pcr_2_chunk_2: u128,
    pcr_2_chunk_3: u128
  }

  struct AttestedData {
    data: u128,
    attestation_timestamp: u128
  }

  struct TimestampedHash {
    request_hash: u128,
    attestation_timestamp: u128
  }

  struct PositionData {
    block_index: u8,
    shift_a: u8,
    shift_b: u8,
    mask_a: u128,
    mask_b: u128
  }

  // owner of a contract
  const owner: address = aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe;

  // unique_id is always stored with 0u8 key
  mapping sgx_unique_id: u8 => UniqueID;

  // pcr_values is always stored with 0u8 key
  mapping nitro_pcr_values: u8 => PcrValues;

  // mapping with allowed enclave keys
  mapping allowed_keys: address => bool;

  // storage of data attested by sgx
  // hash of notarization request with timestamp => Attested_Data
  // or just hash of notarization request for the lastest stored data
  mapping sgx_attested_data: u128 => AttestedData;

  // storage of data attested by sgx
  // hash of notarization request with timestamp => Attested_Data
  // or just hash of notarization request for the lastest stored data
  mapping nitro_attested_data: u128 => AttestedData;

  // set new enclave unique id which is going to be used to verify sgx report
  async transition set_unique_id(public unique_id: UniqueID) -> Future {
    // only owner can change enclave unique id
    assert_eq(owner, self.caller);

    return finalize_set_unique_id(unique_id);
  }

  async function finalize_set_unique_id(public unique_id: UniqueID) {
    Mapping::set(sgx_unique_id, 0u8, unique_id);
  }

  async transition set_pcr_values(public pcr_values: PcrValues) -> Future {
    // only owner can change pcr values
    assert_eq(owner, self.caller);

    return finalize_set_pcr_values(pcr_values);
  }

  async function finalize_set_pcr_values(public pcr_values: PcrValues) {
    Mapping::set(nitro_pcr_values, 0u8, pcr_values);
  }

  async transition set_key(
    public pub_key: address,
    public allowed: bool
  ) -> Future {
    // only owner can change list of allowed keys
    assert_eq(owner, self.caller);

    return finalize_set_key(
      pub_key,
      allowed
    );
  }

  async function finalize_set_key(
    public pub_key: address,
    public allowed: bool
  ) {
    Mapping::set(allowed_keys, pub_key, allowed);
  }

  function extract_value(
    a: u128,
    b: u128,
    position_info: PositionData
  ) -> u128 {
    let a_masked: u128 = a & position_info.mask_a;
    let a_masked_shifted: u128 = a_masked.shr_wrapped(position_info.shift_a);

    let b_masked: u128 = b & position_info.mask_b;
    let b_masked_shifted: u128 = b_masked.shl_wrapped(position_info.shift_b);

    let result: u128 = a_masked_shifted | b_masked_shifted;

    return result;
  }

  function select_chunk(c: DataChunk, pos: u8) -> (u128, u128, u128, u128) {
    if pos == 0u8 {
      return (c.f0, c.f1, c.f2, c.f3);
    }
    if pos == 1u8 {
      return (c.f1, c.f2, c.f3, c.f4);
    }
    if pos == 2u8 {
      return (c.f2, c.f3, c.f4, c.f5);
    }
    if pos == 3u8 {
      return (c.f3, c.f4, c.f5, c.f6);
    }
    if pos == 4u8 {
      return (c.f4, c.f5, c.f6, c.f7);
    }
    if pos == 5u8 {
      return (c.f5, c.f6, c.f7, c.f8);
    }
    if pos == 6u8 {
      return (c.f6, c.f7, c.f8, c.f9);
    }
    if pos == 7u8 {
      return (c.f7, c.f8, c.f9, c.f10);
    }
    if pos == 8u8 {
      return (c.f8, c.f9, c.f10, c.f11);
    }
    if pos == 9u8 {
      return (c.f9, c.f10, c.f11, c.f12);
    }
    if pos == 10u8 {
      return (c.f10, c.f11, c.f12, c.f13);
    }
    if pos == 11u8 {
      return (c.f11, c.f12, c.f13, c.f14);
    }
    if pos == 12u8 {
      return (c.f12, c.f13, c.f14, c.f15);
    }
    if pos == 13u8 {
      return (c.f13, c.f14, c.f15, c.f16);
    }
    if pos == 14u8 {
      return (c.f14, c.f15, c.f16, c.f17);
    }
    if pos == 15u8 {
      return (c.f15, c.f16, c.f17, c.f18);
    }
    if pos == 16u8 {
      return (c.f16, c.f17, c.f18, c.f19);
    }
    if pos == 17u8 {
      return (c.f17, c.f18, c.f19, c.f20);
    }
    if pos == 18u8 {
      return (c.f18, c.f19, c.f20, c.f21);
    }
    if pos == 19u8 {
      return (c.f19, c.f20, c.f21, c.f22);
    }
    if pos == 20u8 {
      return (c.f20, c.f21, c.f22, c.f23);
    }
    if pos == 21u8 {
      return (c.f21, c.f22, c.f23, c.f24);
    }
    if pos == 22u8 {
      return (c.f22, c.f23, c.f24, c.f25);
    }
    if pos == 23u8 {
      return (c.f23, c.f24, c.f25, c.f26);
    }
    if pos == 24u8 {
      return (c.f24, c.f25, c.f26, c.f27);
    }
    if pos == 25u8 {
      return (c.f25, c.f26, c.f27, c.f28);
    }
    if pos == 26u8 {
      return (c.f26, c.f27, c.f28, c.f29);
    }
    if pos == 27u8 {
      return (c.f27, c.f28, c.f29, c.f30);
    }
    if pos == 28u8 {
      return (c.f28, c.f29, c.f30, c.f31);
    }
    if pos == 29u8 {
      return (c.f29, c.f30, c.f31, 0u128);
    }
    if pos == 30u8 {
      return (c.f30, c.f31, 0u128, 0u128);
    }

    return (0u128, 0u128, 0u128, 0u128);
  }

  function get_request_hash(report_data: ReportData) -> u128 {
    let first_data_chunk: DataChunk = DataChunk {
      f0: report_data.c0.f0,
      f1: report_data.c0.f1,
      f2: 0u128,
      f3: 0u128,
      f4: report_data.c0.f4,
      f5: report_data.c0.f5,
      f6: report_data.c0.f6,
      f7: report_data.c0.f7,
      f8: report_data.c0.f8,
      f9: report_data.c0.f9,
      f10: report_data.c0.f10,
      f11: report_data.c0.f11,
      f12: report_data.c0.f12,
      f13: report_data.c0.f13,
      f14: report_data.c0.f14,
      f15: report_data.c0.f15,
      f16: report_data.c0.f16,
      f17: report_data.c0.f17,
      f18: report_data.c0.f18,
      f19: report_data.c0.f19,
      f20: report_data.c0.f20,
      f21: report_data.c0.f21,
      f22: report_data.c0.f22,
      f23: report_data.c0.f23,
      f24: report_data.c0.f24,
      f25: report_data.c0.f25,
      f26: report_data.c0.f26,
      f27: report_data.c0.f27,
      f28: report_data.c0.f28,
      f29: report_data.c0.f29,
      f30: report_data.c0.f30,
      f31: report_data.c0.f31
    };

    let request_data: ReportData = ReportData {
      c0: first_data_chunk,
      c1: report_data.c1,
      c2: report_data.c2,
      c3: report_data.c3,
      c4: report_data.c4,
      c5: report_data.c5,
      c6: report_data.c6,
      c7: report_data.c7
    };

    let request_hash: u128 = Poseidon8::hash_to_u128(request_data);

    return request_hash;
  }

  function verify_sgx_report(
    report_data: ReportData,
    report: Report,
    sig: signature,
    pub_key: address
  ) {
    // https://github.com/openenclave/openenclave/blob/e9a0423e3a0b242bccbe0b5b576e88b640f88f85/include/openenclave/bits/sgx/sgxtypes.h#L1088
    // verify enclave flags
    // chunk 0 field 7 contains enclave flags
    let enclave_flags: u128 = report.c0.f7;
    assert_eq(enclave_flags & 1u128, 1u128); // enclave initted
    assert_eq(enclave_flags & 2u128, 0u128); // enclave is not in debug mode
    assert_eq(enclave_flags & 4u128, 4u128); // enclave is in 64-bit mode

    let data_hash: u128 = Poseidon8::hash_to_u128(report_data);

    // verify that the hash of the data signed by TEE and is included in the report
    assert_eq(data_hash, report.c0.f24);
    assert_eq(0u128, report.c0.f25);
    assert_eq(0u128, report.c0.f26);
    assert_eq(0u128, report.c0.f27);

    let report_hash: u128 = Poseidon8::hash_to_u128(report);

    // verify that the report was signed by TEE
    let is_valid: bool = signature::verify(sig, pub_key, report_hash);
    assert(is_valid);
  }

  function verify_nitro_report(
    report_data: ReportData,
    report: Report,
    sig: signature,
    pub_key: address,
    hash_from_report: u128
  ) {
    let report_data_hash: u128 = Poseidon8::hash_to_u128(report_data);
    assert_eq(report_data_hash, hash_from_report);

    let report_hash: u128 = Poseidon8::hash_to_u128(report);

    // verify that the report was signed by TEE
    let is_valid: bool = signature::verify(sig, pub_key, report_hash);
    assert(is_valid);
  }

  async transition set_data_sgx(
    public report_data: ReportData,
    public report: Report,
    public sig: signature,
    public pub_key: address
  ) -> Future {
    verify_sgx_report(report_data, report, sig, pub_key);

    let request_hash: u128 = get_request_hash(report_data);

    let struct_to_hash: TimestampedHash = TimestampedHash {
      request_hash: request_hash,
      attestation_timestamp: report_data.c0.f3
    };

    let timestamped_hash: u128 = Poseidon8::hash_to_u128(struct_to_hash);

    let attested_data: AttestedData = AttestedData {
      data: report_data.c0.f2,
      attestation_timestamp: report_data.c0.f3
    };

    return finalize_set_data_sgx(
      request_hash,
      timestamped_hash,
      attested_data,
      report.c0.f8,
      report.c0.f9,
      pub_key
    );
  }

  async function finalize_set_data_sgx(
    public request_hash: u128,
    public timestamped_hash: u128,
    public attested_data: AttestedData,
    public report_unique_id_1: u128,
    public report_unique_id_2: u128,
    public pub_key: address
  ) {
    let pub_key_allowed: bool = Mapping::get_or_use(allowed_keys, pub_key, false);
    assert(pub_key_allowed);

    // verify unique id from the TEE report
    let unique_id: UniqueID = Mapping::get(sgx_unique_id, 0u8);
    assert_eq(unique_id.chunk_1, report_unique_id_1);
    assert_eq(unique_id.chunk_2, report_unique_id_2);

    Mapping::set(sgx_attested_data, timestamped_hash, attested_data);

    // replace latest data if current data is newer
    let latest_data: AttestedData = Mapping::get_or_use(sgx_attested_data, request_hash, AttestedData { data: 0u128, attestation_timestamp: 0u128 });

    if (attested_data.attestation_timestamp > latest_data.attestation_timestamp) {
      Mapping::set(sgx_attested_data, request_hash, attested_data);
    }
  }

  async transition set_data_nitro(
    public report_data: ReportData,
    public report: Report,
    public sig: signature,
    public pub_key: address,
    public data_position: PositionData,
    public pcr_0_position: PositionData,
    public pcr_1_position: PositionData,
    public pcr_2_position: PositionData
  ) -> Future {
    let hash_blocks: (u128, u128, u128, u128) = select_chunk(report.c8, data_position.block_index);
    let data_hash_from_report: u128 = extract_value(hash_blocks.0, hash_blocks.1, data_position);

    verify_nitro_report(report_data, report, sig, pub_key, data_hash_from_report);

    let request_hash: u128 = get_request_hash(report_data);

    let struct_to_hash: TimestampedHash = TimestampedHash {
      request_hash: request_hash,
      attestation_timestamp: report_data.c0.f3
    };

    let timestamped_hash: u128 = Poseidon8::hash_to_u128(struct_to_hash);

    let attested_data: AttestedData = AttestedData {
      data: report_data.c0.f2,
      attestation_timestamp: report_data.c0.f3
    };

    let pcr_0_block: (u128, u128, u128, u128) = select_chunk(report.c0, pcr_0_position.block_index);
    let pcr_0_chunk_1: u128 = extract_value(pcr_0_block.0, pcr_0_block.1, pcr_0_position);
    let pcr_0_chunk_2: u128 = extract_value(pcr_0_block.1, pcr_0_block.2, pcr_0_position);
    let pcr_0_chunk_3: u128 = extract_value(pcr_0_block.2, pcr_0_block.3, pcr_0_position);

    let pcr_1_block: (u128, u128, u128, u128) = select_chunk(report.c0, pcr_1_position.block_index);
    let pcr_1_chunk_1: u128 = extract_value(pcr_1_block.0, pcr_1_block.1, pcr_1_position);
    let pcr_1_chunk_2: u128 = extract_value(pcr_1_block.1, pcr_1_block.2, pcr_1_position);
    let pcr_1_chunk_3: u128 = extract_value(pcr_1_block.2, pcr_1_block.3, pcr_1_position);

    let pcr_2_block: (u128, u128, u128, u128) = select_chunk(report.c0, pcr_2_position.block_index);
    let pcr_2_chunk_1: u128 = extract_value(pcr_2_block.0, pcr_2_block.1, pcr_2_position);
    let pcr_2_chunk_2: u128 = extract_value(pcr_2_block.1, pcr_2_block.2, pcr_2_position);
    let pcr_2_chunk_3: u128 = extract_value(pcr_2_block.2, pcr_2_block.3, pcr_2_position);

    let pcr_values: PcrValues = PcrValues {
      pcr_0_chunk_1: pcr_0_chunk_1,
      pcr_0_chunk_2: pcr_0_chunk_2,
      pcr_0_chunk_3: pcr_0_chunk_3,
      pcr_1_chunk_1: pcr_1_chunk_1,
      pcr_1_chunk_2: pcr_1_chunk_2,
      pcr_1_chunk_3: pcr_1_chunk_3,
      pcr_2_chunk_1: pcr_2_chunk_1,
      pcr_2_chunk_2: pcr_2_chunk_2,
      pcr_2_chunk_3: pcr_2_chunk_3
    };

    return finalize_set_data_nitro(
      request_hash,
      timestamped_hash,
      attested_data,
      pcr_values,
      pub_key
    );
  }

  async function finalize_set_data_nitro(
    public request_hash: u128,
    public timestamped_hash: u128,
    public attested_data: AttestedData,
    public report_pcr_values: PcrValues,
    public pub_key: address
  ) {
    let pub_key_allowed: bool = Mapping::get_or_use(allowed_keys, pub_key, false);
    assert(pub_key_allowed);

    let pcr_values: PcrValues = Mapping::get(nitro_pcr_values, 0u8);
    assert_eq(pcr_values, report_pcr_values);

    Mapping::set(nitro_attested_data, timestamped_hash, attested_data);

    // replace latest data if current data is newer
    let latest_data: AttestedData = Mapping::get_or_use(nitro_attested_data, request_hash, AttestedData { data: 0u128, attestation_timestamp: 0u128 });

    if (attested_data.attestation_timestamp > latest_data.attestation_timestamp) {
      Mapping::set(nitro_attested_data, request_hash, attested_data);
    }
  }

  // a 512-byte data chunk
  struct DataChunk {
    f0: u128,
    f1: u128,
    f2: u128,
    f3: u128,
    f4: u128,
    f5: u128,
    f6: u128,
    f7: u128,
    f8: u128,
    f9: u128,
    f10: u128,
    f11: u128,
    f12: u128,
    f13: u128,
    f14: u128,
    f15: u128,
    f16: u128,
    f17: u128,
    f18: u128,
    f19: u128,
    f20: u128,
    f21: u128,
    f22: u128,
    f23: u128,
    f24: u128,
    f25: u128,
    f26: u128,
    f27: u128,
    f28: u128,
    f29: u128,
    f30: u128,
    f31: u128
  }

  struct Report {
    c0: DataChunk,
    c1: DataChunk,
    c2: DataChunk,
    c3: DataChunk,
    c4: DataChunk,
    c5: DataChunk,
    c6: DataChunk,
    c7: DataChunk,
    c8: DataChunk,
    c9: DataChunk
  }

  struct ReportData {
    c0: DataChunk,
    c1: DataChunk,
    c2: DataChunk,
    c3: DataChunk,
    c4: DataChunk,
    c5: DataChunk,
    c6: DataChunk,
    c7: DataChunk
  }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
program official_oracle.aleo;

struct UniqueID:
    chunk_1 as u128;
    chunk_2 as u128;

struct PcrValues:
    pcr_0_chunk_1 as u128;
    pcr_0_chunk_2 as u128;
    pcr_0_chunk_3 as u128;
    pcr_1_chunk_1 as u128;
    pcr_1_chunk_2 as u128;
    pcr_1_chunk_3 as u128;
    pcr_2_chunk_1 as u128;
    pcr_2_chunk_2 as u128;
    pcr_2_chunk_3 as u128;

struct AttestedData:
    data as u128;
    attestation_timestamp as u128;

struct TimestampedHash:
    request_hash as u128;
    attestation_timestamp as u128;

struct PositionData:
    block_index as u8;
    shift_a as u8;
    shift_b as u8;
    mask_a as u128;
    mask_b as u128;

struct DataChunk:
    f0 as u128;
    f1 as u128;
    f2 as u128;
    f3 as u128;
    f4 as u128;
    f5 as u128;
    f6 as u128;
    f7 as u128;
    f8 as u128;
    f9 as u128;
    f10 as u128;
    f11 as u128;
    f12 as u128;
    f13 as u128;
    f14 as u128;
    f15 as u128;
    f16 as u128;
    f17 as u128;
    f18 as u128;
    f19 as u128;
    f20 as u128;
    f21 as u128;
    f22 as u128;
    f23 as u128;
    f24 as u128;
    f25 as u128;
    f26 as u128;
    f27 as u128;
    f28 as u128;
    f29 as u128;
    f30 as u128;
    f31 as u128;

struct Report:
    c0 as DataChunk;
    c1 as DataChunk;
    c2 as DataChunk;
    c3 as DataChunk;
    c4 as DataChunk;
    c5 as DataChunk;
    c6 as DataChunk;
    c7 as DataChunk;
    c8 as DataChunk;
    c9 as DataChunk;

struct ReportData:
    c0 as DataChunk;
    c1 as DataChunk;
    c2 as DataChunk;
    c3 as DataChunk;
    c4 as DataChunk;
    c5 as DataChunk;
    c6 as DataChunk;
    c7 as DataChunk;


mapping sgx_unique_id:
  key as u8.public;
  value as UniqueID.public;


mapping nitro_pcr_values:
  key as u8.public;
  value as PcrValues.public;


mapping allowed_keys:
  key as address.public;
  value as boolean.public;


mapping sgx_attested_data:
  key as u128.public;
  value as AttestedData.public;


mapping nitro_attested_data:
  key as u128.public;
  value as AttestedData.public;


function set_unique_id:
    input r0 as UniqueID.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_unique_id r0 into r1;
    output r1 as official_oracle.aleo/set_unique_id.future;

finalize set_unique_id:
    input r0 as UniqueID.public;
    set r0 into sgx_unique_id[0u8];




function set_pcr_values:
    input r0 as PcrValues.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_pcr_values r0 into r1;
    output r1 as official_oracle.aleo/set_pcr_values.future;

finalize set_pcr_values:
    input r0 as PcrValues.public;
    set r0 into nitro_pcr_values[0u8];




function set_key:
    input r0 as address.public;
    input r1 as boolean.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_key r0 r1 into r2;
    output r2 as official_oracle.aleo/set_key.future;

finalize set_key:
    input r0 as address.public;
    input r1 as boolean.public;
    set r1 into allowed_keys[r0];



closure verify_sgx_report:
    input r0 as ReportData;
    input r1 as Report;
    input r2 as signature;
    input r3 as address;
    and r1.c0.f7 1u128 into r4;
    assert.eq r4 1u128;
    and r1.c0.f7 2u128 into r5;
    assert.eq r5 0u128;
    and r1.c0.f7 4u128 into r6;
    assert.eq r6 4u128;
    hash.psd8 r0 into r7 as u128;
    assert.eq r7 r1.c0.f24;
    assert.eq 0u128 r1.c0.f25;
    assert.eq 0u128 r1.c0.f26;
    assert.eq 0u128 r1.c0.f27;
    hash.psd8 r1 into r8 as u128;
    sign.verify r2 r3 r8 into r9;
    assert.eq r9 true;


closure get_request_hash:
    input r0 as ReportData;
    cast r0.c0.f0 r0.c0.f1 0u128 0u128 r0.c0.f4 r0.c0.f5 r0.c0.f6 r0.c0.f7 r0.c0.f8 r0.c0.f9 r0.c0.f10 r0.c0.f11 r0.c0.f12 r0.c0.f13 r0.c0.f14 r0.c0.f15 r0.c0.f16 r0.c0.f17 r0.c0.f18 r0.c0.f19 r0.c0.f20 r0.c0.f21 r0.c0.f22 r0.c0.f23 r0.c0.f24 r0.c0.f25 r0.c0.f26 r0.c0.f27 r0.c0.f28 r0.c0.f29 r0.c0.f30 r0.c0.f31 into r1 as DataChunk;
    cast r1 r0.c1 r0.c2 r0.c3 r0.c4 r0.c5 r0.c6 r0.c7 into r2 as ReportData;
    hash.psd8 r2 into r3 as u128;
    output r3 as u128;



function set_data_sgx:
    input r0 as ReportData.public;
    input r1 as Report.public;
    input r2 as signature.public;
    input r3 as address.public;
    call verify_sgx_report r0 r1 r2 r3;
    call get_request_hash r0 into r4;
    cast r4 r0.c0.f3 into r5 as TimestampedHash;
    hash.psd8 r5 into r6 as u128;
    cast r0.c0.f2 r0.c0.f3 into r7 as AttestedData;
    async set_data_sgx r4 r6 r7 r1.c0.f8 r1.c0.f9 r3 into r8;
    output r8 as official_oracle.aleo/set_data_sgx.future;

finalize set_data_sgx:
    input r0 as u128.public;
    input r1 as u128.public;
    input r2 as AttestedData.public;
    input r3 as u128.public;
    input r4 as u128.public;
    input r5 as address.public;
    get.or_use allowed_keys[r5] false into r6;
    assert.eq r6 true;
    get sgx_unique_id[0u8] into r7;
    assert.eq r7.chunk_1 r3;
    assert.eq r7.chunk_2 r4;
    set r2 into sgx_attested_data[r1];
    cast 0u128 0u128 into r8 as AttestedData;
    get.or_use sgx_attested_data[r0] r8 into r9;
    gt r2.attestation_timestamp r9.attestation_timestamp into r10;
    branch.eq r10 false to end_then_0_0;
    set r2 into sgx_attested_data[r0];
    branch.eq true true to end_otherwise_0_1;
    position end_then_0_0;
    position end_otherwise_0_1;



closure select_chunk:
    input r0 as DataChunk;
    input r1 as u8;
    is.eq r1 0u8 into r2;
    is.eq r1 1u8 into r3;
    is.eq r1 2u8 into r4;
    is.eq r1 3u8 into r5;
    is.eq r1 4u8 into r6;
    is.eq r1 5u8 into r7;
    is.eq r1 6u8 into r8;
    is.eq r1 7u8 into r9;
    is.eq r1 8u8 into r10;
    is.eq r1 9u8 into r11;
    is.eq r1 10u8 into r12;
    is.eq r1 11u8 into r13;
    is.eq r1 12u8 into r14;
    is.eq r1 13u8 into r15;
    is.eq r1 14u8 into r16;
    is.eq r1 15u8 into r17;
    is.eq r1 16u8 into r18;
    is.eq r1 17u8 into r19;
    is.eq r1 18u8 into r20;
    is.eq r1 19u8 into r21;
    is.eq r1 20u8 into r22;
    is.eq r1 21u8 into r23;
    is.eq r1 22u8 into r24;
    is.eq r1 23u8 into r25;
    is.eq r1 24u8 into r26;
    is.eq r1 25u8 into r27;
    is.eq r1 26u8 into r28;
    is.eq r1 27u8 into r29;
    is.eq r1 28u8 into r30;
    is.eq r1 29u8 into r31;
    is.eq r1 30u8 into r32;
    ternary r32 r0.f30 0u128 into r33;
    ternary r32 r0.f31 0u128 into r34;
    ternary r32 0u128 0u128 into r35;
    ternary r32 0u128 0u128 into r36;
    ternary r31 r0.f29 r33 into r37;
    ternary r31 r0.f30 r34 into r38;
    ternary r31 r0.f31 r35 into r39;
    ternary r31 0u128 r36 into r40;
    ternary r30 r0.f28 r37 into r41;
    ternary r30 r0.f29 r38 into r42;
    ternary r30 r0.f30 r39 into r43;
    ternary r30 r0.f31 r40 into r44;
    ternary r29 r0.f27 r41 into r45;
    ternary r29 r0.f28 r42 into r46;
    ternary r29 r0.f29 r43 into r47;
    ternary r29 r0.f30 r44 into r48;
    ternary r28 r0.f26 r45 into r49;
    ternary r28 r0.f27 r46 into r50;
    ternary r28 r0.f28 r47 into r51;
    ternary r28 r0.f29 r48 into r52;
    ternary r27 r0.f25 r49 into r53;
    ternary r27 r0.f26 r50 into r54;
    ternary r27 r0.f27 r51 into r55;
    ternary r27 r0.f28 r52 into r56;
    ternary r26 r0.f24 r53 into r57;
    ternary r26 r0.f25 r54 into r58;
    ternary r26 r0.f26 r55 into r59;
    ternary r26 r0.f27 r56 into r60;
    ternary r25 r0.f23 r57 into r61;
    ternary r25 r0.f24 r58 into r62;
    ternary r25 r0.f25 r59 into r63;
    ternary r25 r0.f26 r60 into r64;
    ternary r24 r0.f22 r61 into r65;
    ternary r24 r0.f23 r62 into r66;
    ternary r24 r0.f24 r63 into r67;
    ternary r24 r0.f25 r64 into r68;
    ternary r23 r0.f21 r65 into r69;
    ternary r23 r0.f22 r66 into r70;
    ternary r23 r0.f23 r67 into r71;
    ternary r23 r0.f24 r68 into r72;
    ternary r22 r0.f20 r69 into r73;
    ternary r22 r0.f21 r70 into r74;
    ternary r22 r0.f22 r71 into r75;
    ternary r22 r0.f23 r72 into r76;
    ternary r21 r0.f19 r73 into r77;
    ternary r21 r0.f20 r74 into r78;
    ternary r21 r0.f21 r75 into r79;
    ternary r21 r0.f22 r76 into r80;
    ternary r20 r0.f18 r77 into r81;
    ternary r20 r0.f19 r78 into r82;
    ternary r20 r0.f20 r79 into r83;
    ternary r20 r0.f21 r80 into r84;
    ternary r19 r0.f17 r81 into r85;
    ternary r19 r0.f18 r82 into r86;
    ternary r19 r0.f19 r83 into r87;
    ternary r19 r0.f20 r84 into r88;
    ternary r18 r0.f16 r85 into r89;
    ternary r18 r0.f17 r86 into r90;
    ternary r18 r0.f18 r87 into r91;
    ternary r18 r0.f19 r88 into r92;
    ternary r17 r0.f15 r89 into r93;
    ternary r17 r0.f16 r90 into r94;
    ternary r17 r0.f17 r91 into r95;
    ternary r17 r0.f18 r92 into r96;
    ternary r16 r0.f14 r93 into r97;
    ternary r16 r0.f15 r94 into r98;
    ternary r16 r0.f16 r95 into r99;
    ternary r16 r0.f17 r96 into r100;
    ternary r15 r0.f13 r97 into r101;
    ternary r15 r0.f14 r98 into r102;
    ternary r15 r0.f15 r99 into r103;
    ternary r15 r0.f16 r100 into r104;
    ternary r14 r0.f12 r101 into r105;
    ternary r14 r0.f13 r102 into r106;
    ternary r14 r0.f14 r103 into r107;
    ternary r14 r0.f15 r104 into r108;
    ternary r13 r0.f11 r105 into r109;
    ternary r13 r0.f12 r106 into r110;
    ternary r13 r0.f13 r107 into r111;
    ternary r13 r0.f14 r108 into r112;
    ternary r12 r0.f10 r109 into r113;
    ternary r12 r0.f11 r110 into r114;
    ternary r12 r0.f12 r111 into r115;
    ternary r12 r0.f13 r112 into r116;
    ternary r11 r0.f9 r113 into r117;
    ternary r11 r0.f10 r114 into r118;
    ternary r11 r0.f11 r115 into r119;
    ternary r11 r0.f12 r116 into r120;
    ternary r10 r0.f8 r117 into r121;
    ternary r10 r0.f9 r118 into r122;
    ternary r10 r0.f10 r119 into r123;
    ternary r10 r0.f11 r120 into r124;
    ternary r9 r0.f7 r121 into r125;
    ternary r9 r0.f8 r122 into r126;
    ternary r9 r0.f9 r123 into r127;
    ternary r9 r0.f10 r124 into r128;
    ternary r8 r0.f6 r125 into r129;
    ternary r8 r0.f7 r126 into r130;
    ternary r8 r0.f8 r127 into r131;
    ternary r8 r0.f9 r128 into r132;
    ternary r7 r0.f5 r129 into r133;
    ternary r7 r0.f6 r130 into r134;
    ternary r7 r0.f7 r131 into r135;
    ternary r7 r0.f8 r132 into r136;
    ternary r6 r0.f4 r133 into r137;
    ternary r6 r0.f5 r134 into r138;
    ternary r6 r0.f6 r135 into r139;
    ternary r6 r0.f7 r136 into r140;
    ternary r5 r0.f3 r137 into r141;
    ternary r5 r0.f4 r138 into r142;
    ternary r5 r0.f5 r139 into r143;
    ternary r5 r0.f6 r140 into r144;
    ternary r4 r0.f2 r141 into r145;
    ternary r4 r0.f3 r142 into r146;
    ternary r4 r0.f4 r143 into r147;
    ternary r4 r0.f5 r144 into r148;
    ternary r3 r0.f1 r145 into r149;
    ternary r3 r0.f2 r146 into r150;
    ternary r3 r0.f3 r147 into r151;
    ternary r3 r0.f4 r148 into r152;
    ternary r2 r0.f0 r149 into r153;
    ternary r2 r0.f1 r150 into r154;
    ternary r2 r0.f2 r151 into r155;
    ternary r2 r0.f3 r152 into r156;
    output r153 as u128;
    output r154 as u128;
    output r155 as u128;
    output r156 as u128;


closure extract_value:
    input r0 as u128;
    input r1 as u128;
    input r2 as PositionData;
    and r0 r2.mask_a into r3;
    shr.w r3 r2.shift_a into r4;
    and r1 r2.mask_b into r5;
    shl.w r5 r2.shift_b into r6;
    or r4 r6 into r7;
    output r7 as u128;


closure verify_nitro_report:
    input r0 as ReportData;
    input r1 as Report;
    input r2 as signature;
    input r3 as address;
    input r4 as u128;
    hash.psd8 r0 into r5 as u128;
    assert.eq r5 r4;
    hash.psd8 r1 into r6 as u128;
    sign.verify r2 r3 r6 into r7;
    assert.eq r7 true;



function set_data_nitro:
    input r0 as ReportData.public;
    input r1 as Report.public;
    input r2 as signature.public;
    input r3 as address.public;
    input r4 as PositionData.public;
    input r5 as PositionData.public;
    input r6 as PositionData.public;
    input r7 as PositionData.public;
    call select_chunk r1.c8 r4.block_index into r8 r9 r10 r11;
    call extract_value r8 r9 r4 into r12;
    call verify_nitro_report r0 r1 r2 r3 r12;
    call get_request_hash r0 into r13;
    cast r13 r0.c0.f3 into r14 as TimestampedHash;
    hash.psd8 r14 into r15 as u128;
    cast r0.c0.f2 r0.c0.f3 into r16 as AttestedData;
    call select_chunk r1.c0 r5.block_index into r17 r18 r19 r20;
    call extract_value r17 r18 r5 into r21;
    call extract_value r18 r19 r5 into r22;
    call extract_value r19 r20 r5 into r23;
    call select_chunk r1.c0 r6.block_index into r24 r25 r26 r27;
    call extract_value r24 r25 r6 into r28;
    call extract_value r25 r26 r6 into r29;
    call extract_value r26 r27 r6 into r30;
    call select_chunk r1.c0 r7.block_index into r31 r32 r33 r34;
    call extract_value r31 r32 r7 into r35;
    call extract_value r32 r33 r7 into r36;
    call extract_value r33 r34 r7 into r37;
    cast r21 r22 r23 r28 r29 r30 r35 r36 r37 into r38 as PcrValues;
    async set_data_nitro r13 r15 r16 r38 r3 into r39;
    output r39 as official_oracle.aleo/set_data_nitro.future;

finalize set_data_nitro:
    input r0 as u128.public;
    input r1 as u128.public;
    input r2 as AttestedData.public;
    input r3 as PcrValues.public;
    input r4 as address.public;
    get.or_use allowed_keys[r4] false into r5;
    assert.eq r5 true;
    get nitro_pcr_values[0u8] into r6;
    assert.eq r6 r3;
    set r2 into nitro_attested_data[r1];
    cast 0u128 0u128 into r7 as AttestedData;
    get.or_use nitro_attested_data[r0] r7 into r8;
    gt r2.attestation_timestamp r8.attestation_timestamp into r9;
    branch.eq r9 false to end_then_0_2;
    set r2 into nitro_attested_data[r0];
    branch.eq true true to end_otherwise_0_3;
    position end_then_0_2;
    position end_otherwise_0_3;

How to use the Oracle#

Here is an example on how to use values stored in the Aleo Oracle.

If you want to use the values outside of the Aleo blockchain then you can simply query the mapping value from the blockchain using the Request Hash as a key:

Querying the Aleo Oracle

curl https://api.explorer.aleo.org/v1/mainnet/program/official_oracle.aleo/mapping/attested_data/{request_hash}

Where request_hash is a Hash of the Request that was used to get data (with zeroed Data and Timestamp to make it static).

To use Aleo Oracle in your Aleo program you need to import the Oracle program first and then get a value from the attested_data mapping. Keep in mind that in Aleo you can only read mappings during the Finalize function.

Querying the Aleo Oracle from Aleo program

Example of a simple program that will transfer an attested amount of some token.

Since you can read mappings only during the finalize stage, in this example we provide the amount as a function parameter to a Transition function to use and then verify that the provided value is correct during the finalize step.

import official_oracle.aleo;

program use_oracle.aleo {
  struct AttestedData {
    data: u128,
    attestation_timestamp: u128
  }

  async transition use_oracle(
    public amount: u128,
    public recipient: address
  ) -> Future {
    // use the provided amount
    // you can query amount with your backend from the blockchain first
    // we can trust the amount because it is going to be verified in the finalize step
    transfer_public(recipient, amount);

    return finalize_use_oracle(amount);
  }

  async function finalize_use_oracle(public amount: u128) {
    let request_hash: u128 = 0u128; // use the desired request hash here
    // read value from the aleo oracle
    // you can also use nitro_attested_data to get the data that was notarized by the Nitro notarizer
    let data_from_oracle: AttestedData = official_oracle.aleo/sgx_attested_data.get_or_use(request_hash, AttestedData { data: 0u128, attestation_timestamp: 0u128 });
    // make sure that it matches the amount used in the transition part
    assert_eq(amount, data_from_oracle.data);
  }
}
import official_oracle.aleo;
program use_oracle.aleo;

struct AttestedData:
    data as u128;
    attestation_timestamp as u128;

function use_oracle:
    input r0 as u128.public;
    input r1 as address.public;
    call transfer_public r1 r0 into r2;
    async use_oracle r0 into r3;
    output r3 as use_oracle.aleo/use_oracle.future;

finalize use_oracle:
    input r0 as u128.public;
    cast 0u128 0u128 into r1 as AttestedData;
    get.or_use official_oracle.aleo/sgx_attested_data[request_hash] r1 into r2;
    assert.eq r0 r2.data;

Where request_hash is the Hash of the Request that was used during the Notarization process (with Data and Timestamp zeroed out to make it static).

Historical data#

In case you want to use the data that was attested at a specific timestamp you can use Timestamped Request Hash as a key to attested data mappings. It contains the Request Hash that was used to get the data as well as the timestamp of when the this data was notarized.

Using timestamped_request_hash to query the Aleo Oracle

Example of a simple program that will transfer an attested amount of some token.

Since you can read mappings only during the finalize stage, in this example we provide the amount as a function parameter to a Transition function to use and then verify that the provided value is correct during the finalize step.

import official_oracle.aleo;

program use_timestamped_hash.aleo {
  struct TimestampedHash {
    request_hash: u128,
    attestation_timestamp: u128
  }

  async function use_timestamped_data(public amount: u128) {
    let request_hash: u128 = 0u128; // use the desired request hash here
    let timestamp: u128 = 0u128; // notarization timestamp

    let struct_to_hash: TimestampedHash = TimestampedHash {
      request_hash: request_hash,
      attestation_timestamp: timestamp
    };

    let timestamped_hash: u128 = Poseidon8::hash_to_u128(struct_to_hash);

    // read value from the aleo oracle
    // you can also use nitro_attested_data to get the data that was notarized by the Nitro notarizer
    let data_from_oracle: AttestedData = official_oracle.aleo/sgx_attested_data.get_or_use(timestamped_hash, AttestedData { data: 0u128, attestation_timestamp: 0u128 });
    // make sure that it matches the amount used in the transition
    assert_eq(amount, data_from_oracle.data);

  }
}
import official_oracle.aleo;
program use_oracle.aleo;

struct AttestedData:
    data as u128;
    attestation_timestamp as u128;

function use_oracle:
    input r0 as u128.public;
    input r1 as address.public;
    call transfer_public r1 r0 into r2;
    async use_oracle r0 into r3;
    output r3 as use_oracle.aleo/use_oracle.future;

finalize use_oracle:
    input r0 as u128.public;
    cast 0u128 0u128 into r1 as AttestedData;
    get.or_use official_oracle.aleo/sgx_attested_data[request_hash] r1 into r2;
    assert.eq r0 r2.data;

Where request_hash is the Hash of the Request that was used during the Notarization process (with Data and Timestamp zeroed out to make it static).

Value type#

Aleo Oracle stores all the values as u128 numbers. In case you require smaller numbers, e.g. u64, you can cast u128 to the desired size.

(!) Keep in mind that a u128 number should be small enough to fit into the desired type (for example u64). A u64 number represents 8 bytes of information while u128 is 16 bytes. So if you want to cast u128 to u64 then the u128 value should only contain not more than 8 bytes of information, not more than 4 bytes for u32, etc. Use this trick only if you know that your data provider gives smaller than u128 responses.

Casting a u128 to a smaller number

1
2
3
4
5
let u128_number: u128 = attested_data.get_or_use(request_hash, 0u128);

let smaller_number: u64 = u128_number as u64;

let even_smaller_number: u64 = u128_number as u32;
1
2
3
get.or_use official_oracle.aleo/attested_data[request_hash] 0u128 into r0;
cast r0 into r1 as u64;
cast r0 into r2 as u32;

Migration guide#

Since the testnet deployment we have updated the oracle to also support reports from another type of TEE enclave.

There's a deployed dual TEE version of the Oracle program in:

They are the same program.

For comparison, here is the old version of the Oracle program in the testnet network - official_oracle.aleo (testnet). This program is not supported anymore.

To migrate to the new dual TEE version of the Oracle program you need to perform the following changes:

  1. Change set_data call to set_data_sgx;
  2. Change reads of the attested_data mapping to sgx_attested_data, which now returns an AttestedData struct instead of the attested data u128 value. Use data property to access the data;
  3. Remove reads of the last_update_timestamp mapping, if you had any.

In addition to having the latest data in the mapping, you can now read the historical (timestamped) data too. See more in set_data_sgx and the Timestamped Request Hash.