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:
    snarkos start --nodisplay --dev 0 --validator
    

For every validator increase --dev by 1

  • 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 and public_key. You can get unique_id and public_key by requesting /info endpoint of the Oracle Notarization backend

    snarkos developer execute official_oracle.aleo set_key <signerPubKey> --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 <unique_id_1> <unique_id_2> --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 source code#

You can find this program deployed in the Aleo Explorer or Aleo Info.

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

Aleo Oracle program
program official_oracle.aleo {
  // mapping with a 32-byte enclave unique_id
  // split in 2 16-byte u128 chunks with keys 1u8 and 2u8
  mapping unique_id: u8 => u128;

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

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

  // notarization request hash => attested data
  mapping attested_data: u128 => u128;

  // notarization request hash => timestamp of last update little endian u128
  mapping last_update_timestamp: u128 => u128;

  function verify_report(
    data: ReportData,
    report: Report,
    sig: signature,
    pub_key: address,
  ) -> bool {
    let data_hash: u128 = Poseidon8::hash_to_u128(data);

    // 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

    // 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
    return signature::verify(sig, pub_key, report_hash);
  }

  // flips provided key status (disabled->enabled or enabled->disabled)
  transition set_key(public pub_key: address) {
    // only owner can change list of allowed keys
    assert_eq(owner, self.caller);

    return then finalize(pub_key);
  }

  finalize set_key(public pub_key: address) {
    let key_status: bool = Mapping::get_or_use(allowed_keys, pub_key, false);

    key_status = key_status ? false : true;

    Mapping::set(allowed_keys, pub_key, key_status);
  }

  // set new enclave unique id which is going to be used to verify report
  transition set_unique_id(public unique_id_1: u128, public unique_id_2: u128) {
    // only owner can change enclave unique id
    assert_eq(owner, self.caller);

    return then finalize(unique_id_1, unique_id_2);
  }

  finalize set_unique_id(public unique_id_1: u128, public unique_id_2: u128) {
    Mapping::set(unique_id, 1u8, unique_id_1);
    Mapping::set(unique_id, 2u8, unique_id_2);
  }

  transition set_data(
    public report_data: ReportData,
    public report: Report,
    public sig: signature,
    public pub_key: address
  ) {
    let is_valid: bool = verify_report(report_data, report, sig, pub_key);
    assert(is_valid);

    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 then finalize(
      request_hash,
      // attested data
      report_data.c0.f2,
      // report timestamp
      report_data.c0.f3,
      pub_key,
      // enclave unique id
      report.c0.f8,
      report.c0.f9
    );
  }

  finalize set_data(
    public request_hash: u128,
    public report_data: u128,
    public timestamp: u128,
    public pub_key: address,
    public report_unique_id_1: u128,
    public report_unique_id_2: u128
  ) {
    let pub_key_allowed: bool = Mapping::get_or_use(allowed_keys, pub_key, false);
    assert(pub_key_allowed);

    // a 32-byte enclave unique id in 2 16-byte chunks
    let unique_id_1: u128 = Mapping::get(unique_id, 1u8);
    let unique_id_2: u128 = Mapping::get(unique_id, 2u8);
    // verify unique id from the TEE report
    assert_eq(unique_id_1, report_unique_id_1);
    assert_eq(unique_id_2, report_unique_id_2);

    // verify that report is never that previous
    // this will not allow to update oracle with outdated data
    let last_timestamp: u128 = Mapping::get_or_use(last_update_timestamp, request_hash, 0u128);
    let is_newer: bool = timestamp > last_timestamp;
    assert(is_newer);

    Mapping::set(attested_data, request_hash, report_data);
    Mapping::set(last_update_timestamp, request_hash, timestamp);
  }

  // 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
  }
}
program official_oracle.aleo;

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 unique_id:
  key as u8.public;
  value as u128.public;


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


mapping attested_data:
  key as u128.public;
  value as u128.public;


mapping last_update_timestamp:
  key as u128.public;
  value as u128.public;

closure verify_report:
    input r0 as ReportData;
    input r1 as Report;
    input r2 as signature;
    input r3 as address;
    hash.psd8 r0 into r4 as u128;
    and r1.c0.f7 1u128 into r5;
    assert.eq r5 1u128;
    and r1.c0.f7 2u128 into r6;
    assert.eq r6 0u128;
    and r1.c0.f7 4u128 into r7;
    assert.eq r7 4u128;
    assert.eq r4 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;
    output r9 as boolean;


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

finalize set_key:
    input r0 as address.public;
    get.or_use allowed_keys[r0] false into r1;
    ternary r1 false true into r2;
    set r2 into allowed_keys[r0];


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

finalize set_unique_id:
    input r0 as u128.public;
    input r1 as u128.public;
    set r0 into unique_id[1u8];
    set r1 into unique_id[2u8];


function set_data:
    input r0 as ReportData.public;
    input r1 as Report.public;
    input r2 as signature.public;
    input r3 as address.public;
    call verify_report r0 r1 r2 r3 into r4;
    assert.eq r4 true;
    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 r5 as DataChunk;
    cast r5 r0.c1 r0.c2 r0.c3 r0.c4 r0.c5 r0.c6 r0.c7 into r6 as ReportData;
    hash.psd8 r6 into r7 as u128;
    async set_data r7 r0.c0.f2 r0.c0.f3 r3 r1.c0.f8 r1.c0.f9 into r8;
    output r8 as official_oracle.aleo/set_data.future;

finalize set_data:
    input r0 as u128.public;
    input r1 as u128.public;
    input r2 as u128.public;
    input r3 as address.public;
    input r4 as u128.public;
    input r5 as u128.public;
    get.or_use allowed_keys[r3] false into r6;
    assert.eq r6 true;
    get unique_id[1u8] into r7;
    get unique_id[2u8] into r8;
    assert.eq r7 r4;
    assert.eq r8 r5;
    get.or_use last_update_timestamp[r0] 0u128 into r9;
    gt r2 r9 into r10;
    assert.eq r10 true;
    set r1 into attested_data[r0];
    set r2 into last_update_timestamp[r0];

Mappings#

unique_id#

unique_id

mapping unique_id: u8 => u128;
1
2
3
mapping unique_id:
  key as u8.public;
  value as u128.public;

unique_id mapping stores 32 bytes of SGX enclave's unique ID split in 2 16-byte u128 formatted chunks with keys 1u8 and 2u8. 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 unique_id Oracle Notarization backend is running by requesting the /info endpoint.

You can also run the Oracle Notarization backend yourself using reproducible builds and see the generated unique ID. It should match the unique_id 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.

attested_data#

attested_data

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

attested_data stores the latest acquired data from a specific request. When new data is being pushed to the program it replaces the old value. You can always access the stored data using your Request Hash as a key.

last_update_timestamp#

last_update_timestamp

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

last_update_timestamp contains a timestamp of the attestation of the data that is stored in attested_data mapping was attested. This timestamp is used to make sure that the current report was created after the last verified report.

Functions#

set_key#

set_key

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

  return then finalize(pub_key);
}

finalize set_key(public pub_key: address) {
  let key_status: bool = Mapping::get_or_use(allowed_keys, pub_key, false);

  key_status = key_status ? false : true;

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

finalize set_key:
  input r0 as address.public;
  get.or_use allowed_keys[r0] false into r1;
  ternary r1 false true into r2;
  set r2 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 flips the value of the key:

  • If key was not present or not allowed then it becomes allowed
  • If key was already allowed then it becomes not allowed

set_unique_id#

set_unique_id

transition set_unique_id(public unique_id_1: u128, public unique_id_2: u128) {
  // only owner can change enclave unique id
  assert_eq(owner, self.caller);

  return then finalize(unique_id_1, unique_id_2);
}

finalize set_unique_id(public unique_id_1: u128, public unique_id_2: u128) {
  Mapping::set(unique_id, 1u8, unique_id_1);
  Mapping::set(unique_id, 2u8, unique_id_2);
}
function set_unique_id:
    input r0 as u128.public;
    input r1 as u128.public;
    assert.eq aleo1urxgwwfph8243x68r2sh772vl55ln0cvzvru4j9nm9er7x40lgyqkrthfe self.caller;
    async set_unique_id r0 r1 into r2;
    output r2 as official_oracle.aleo/set_unique_id.future;

finalize set_unique_id:
    input r0 as u128.public;
    input r1 as u128.public;
    set r0 into unique_id[1u8];
    set r1 into unique_id[2u8];

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.

verify_report#

verify_report

function verify_report(
  data: ReportData,
  report: Report,
  sig: signature,
  pub_key: address,
) -> bool {
  let data_hash: u128 = Poseidon8::hash_to_u128(data);

  // 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

  // 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
  return signature::verify(sig, pub_key, report_hash);
}
closure verify_report:
  input r0 as ReportData;
  input r1 as Report;
  input r2 as signature;
  input r3 as address;
  hash.psd8 r0 into r4 as u128;
  and r1.c0.f7 1u128 into r5;
  assert.eq r5 1u128;
  and r1.c0.f7 2u128 into r6;
  assert.eq r6 0u128;
  and r1.c0.f7 4u128 into r7;
  assert.eq r7 4u128;
  assert.eq r4 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;
  output r9 as boolean;

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.

set_data#

set_data
transition set_data(
  public report_data: ReportData,
  public report: Report,
  public sig: signature,
  public pub_key: address
) {
  let is_valid: bool = verify_report(report_data, report, sig, pub_key);
  assert(is_valid);

  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 then finalize(
    request_hash,
    // attested data
    report_data.c0.f2,
    // report timestamp
    report_data.c0.f3,
    pub_key,
    // enclave unique id
    report.c0.f8,
    report.c0.f9
  );
}

finalize set_data(
  public request_hash: u128,
  public report_data: u128,
  public timestamp: u128,
  public pub_key: address,
  public report_unique_id_1: u128,
  public report_unique_id_2: u128
) {
  let pub_key_allowed: bool = Mapping::get_or_use(allowed_keys, pub_key, false);
  assert(pub_key_allowed);

  // a 32-byte enclave unique id in 2 16-byte chunks
  let unique_id_1: u128 = Mapping::get(unique_id, 1u8);
  let unique_id_2: u128 = Mapping::get(unique_id, 2u8);
  // verify unique id from the TEE report
  assert_eq(unique_id_1, report_unique_id_1);
  assert_eq(unique_id_2, report_unique_id_2);

  // verify that report is never that previous
  // this will not allow to update oracle with outdated data
  let last_timestamp: u128 = Mapping::get_or_use(last_update_timestamp, request_hash, 0u128);
  let is_newer: bool = timestamp > last_timestamp;
  assert(is_newer);

  Mapping::set(attested_data, request_hash, report_data);
  Mapping::set(last_update_timestamp, request_hash, timestamp);
}
function set_data:
  input r0 as ReportData.public;
  input r1 as Report.public;
  input r2 as signature.public;
  input r3 as address.public;
  call verify_report r0 r1 r2 r3 into r4;
  assert.eq r4 true;
  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 r5 as DataChunk;
  cast r5 r0.c1 r0.c2 r0.c3 r0.c4 r0.c5 r0.c6 r0.c7 into r6 as ReportData;
  hash.psd8 r6 into r7 as u128;
  async set_data r7 r0.c0.f2 r0.c0.f3 r3 r1.c0.f8 r1.c0.f9 into r8;
  output r8 as official_oracle.aleo/set_data.future;

finalize set_data:
  input r0 as u128.public;
  input r1 as u128.public;
  input r2 as u128.public;
  input r3 as address.public;
  input r4 as u128.public;
  input r5 as u128.public;
  get.or_use allowed_keys[r3] false into r6;
  assert.eq r6 true;
  get unique_id[1u8] into r7;
  get unique_id[2u8] into r8;
  assert.eq r7 r4;
  assert.eq r8 r5;
  get.or_use last_update_timestamp[r0] 0u128 into r9;
  gt r2 r9 into r10;
  assert.eq r10 true;
  set r1 into attested_data[r0];
  set r2 into last_update_timestamp[r0];

This function is used to update the Oracle program with the new data. 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_report) and its signature, then it creates the request_hash by creating a new structure with the attestation data and timestamp zeroed out. This hash is going to be used as a key to store the provided attestation_data. You can read more about how the Request Hash works here.

In the finalize step the Oracle verifies that the uniquie_id of the report is correct and the public_key provided for signature verification is allowed. Then it checks that the timestamp of the report creation is newer than the time of the last provided report to make sure the data is not outdated. And then it saves data into the attested_data mapping and updates last_update_timestamp with the timestamp of the provided report.

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/testnet/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 the Aleo Oracle program
import official_oracle.leo;

transition use_oracle(
  public amount: u128,
  public recipient: address
) {
  // 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 then finalize(amount);
}

finalize use_oracle(public amount: u128) {
  // read value from the aleo oracle
  let amount_from_oracle: u128 = attested_data.get_or_use(request_hash, 0u128);
  // make sure that it matches the amount used in the transition part
  assert_eq(amount, amount_from_oracle);
}
import official_oracle.aleo;

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 example_program.aleo/use_oracle.future;

finalize use_oracle:
  input r0 as u128.public;
  get.or_use official_oracle.aleo/attested_data[request_hash] 0u128 into r1;
  assert.eq r0 r1;

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).

Data timestamp#

In case you only want to use data that is not older than a certain timestamp you can use the last_update_timestamp mapping from the Oracle. It contains the timestamp of the last report that was used to set data.

Using last_update_timestamp

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

// use oracle only if timestamp is greater than certain timestamp
let is_allowed: bool = last_timestamp.gte(12345u128);
assert(is_allowed);
1
2
3
get.or_use official_oracle.aleo/last_update_timestamp[request_hash] 0u128 into r0;
gte r0 12345u128 into r1;
assert.eq r1 true;

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;