Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
simon-something committed Oct 3, 2023
0 parents commit afe8cb4
Show file tree
Hide file tree
Showing 12 changed files with 2,603 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
defi-wonderland/aztec-codeowner
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# PM files:
node_modules
yarn-error.log

# build
target/

# MacOS frens:
.DS_Store
9 changes: 9 additions & 0 deletions Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "privateoracle"
type = "contract"
authors = ["wonderland"]
compiler_version = "0.9.0"

[dependencies]
aztec = {path = "./node_modules/aztec-packages/yarn-project/aztec-nr/aztec"}
value_note = {path = "./node_modules/aztec-packages/yarn-project/aztec-nr/value-note"}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# aztec-contracts


noirup -v aztec
26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "private-oracle",
"version": "0.0.0",
"type": "module",
"license": "MIT",
"types": "module",
"scripts": {
"test": "mocha --require ts-node/register 'src/**/*.test.ts'"
},
"dependencies": {
"@aztec/aztec.js": "^0.7.10",
"@types/mocha": "^10.0.2",
"aztec-packages": "https://github.com/AztecProtocol/aztec-packages",
"mocha": "^10.2.0",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
},
"mocha": {
"require": "ts-node/register",
"extension": [
"ts"
],
"spec": "./src/**/*.test.ts"
}

}
238 changes: 238 additions & 0 deletions src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
mod types;

/**
This first version is "free for use" (no fee) and "free for all" (anyone can answer to any question).
Overall design:
- Requester Alice: I would like Bob to tell me X. Here is the fee.
- Bob: Here is the answer Y to Alice, the fee is now mine
or
- Alice: nvm, I'm good, cancel and give me my money back
Bob is whoever Alice wants.
If Charlie asks X to Bob, Bob will answer Y too.
Eve cannot know anything beside: the token to use, the fee amount
Bob can withdraw it's token whenever.
Side-side-quest
If possible, Bob can post a proof of knowledge ("here is the proof that the thumbnail Z correspond to the high res picture Y")
Tech TL;DR with stuff to clarify:
Create request:
- Fee: value_note balanceOf[Alice] += delta token
- Request:
-- Alice request notes: { request, Alice PK, Bob, randomSalt } - nullifier: Pedersen(note_hash, randomSalt)
-- Bob request notes: adding the same note - nullifier is the same (P(note_hash, randomSalt))
Answer:
- Fee: Should be the same nullifier pattern (then nullify Alice value_note and create for Bob)?
- Check if previously answered (then reuse it, if not, use the answer passed as arg)
- Replace note for a note with the answer
- Add note to Alice notes, with the answer
--> nullifier should be different here -> another note type (answer_note)
*/

contract PrivateOracle {
use crate::types::question_note:: {QuestionNote, QuestionNoteMethods, QUESTION_NOTE_LEN};
use crate::types::answer_note:: {AnswerNote, AnswerNoteMethods, ANSWER_NOTE_LEN};

use dep::std::option::Option;

use dep::aztec::context::Context;
use dep::aztec::log::emit_encrypted_log;
use dep::aztec::note::{
note_header::NoteHeader,
note_interface::NoteInterface,
note_getter_options::NoteGetterOptions,
note_viewer_options::NoteViewerOptions,
utils::compute_note_hash_for_read_or_nullify,
};
use dep::aztec::oracle::{
get_public_key::get_public_key,
rand::rand
};
use dep::aztec::state_vars::public_state::PublicState;
use dep::aztec::state_vars::map::Map;
use dep::aztec::state_vars::set::Set;
use dep::aztec::state_vars::singleton::Singleton;
use dep::aztec::state_vars::immutable_singleton::ImmutableSingleton;
use dep::aztec::types::type_serialization::field_serialization::{ FieldSerializationMethods, FIELD_SERIALIZED_LEN};

struct Storage {
// payment_token: PublicState<Field, FIELD_SERIALIZED_LEN>,
// fee: PublicState<Field, FIELD_SERIALIZED_LEN>,
// stored_balance: PublicState<Field, FIELD_SERIALIZED_LEN>,
// balance_of: Map<Set<ValueNote, VALUE_NOTE_LEN>>,
questions: Set<QuestionNote, QUESTION_NOTE_LEN>,
answers: Set<AnswerNote, ANSWER_NOTE_LEN> // TODO: use a set of immutable singletons
}

impl Storage {
fn init(context: Context) -> pub Self {
Storage {
// -- Public --

// -- Private --
// Set of questions
questions: Set::new(context, 6, QuestionNoteMethods),
// Set of answers
answers: Set::new(context, 7, AnswerNoteMethods)
}
}
}

// Constructs the contract.
#[aztec(private)]
fn constructor() {
}

// Requester submit a question
#[aztec(private)]
fn submit_question(question: Field, divinity_address: Field) {
let storage = Storage::init(Context::private(&mut context));
let address_this = context.this_address();
let sender = context.msg_sender();

// Assert if question doesn't already exist from the same requester
let question_getter_option = NoteGetterOptions::new().select(0, question).set_limit(1);
let question_note = storage.questions.get_notes(question_getter_option)[0];
assert(question_note.is_none());

// Store the question in the requester notes and send it to the oracle notes too
let random_nullifier_shared_key = rand();

let mut newquestion: QuestionNote = QuestionNote::new(
question,
sender,
divinity_address,
random_nullifier_shared_key
);

storage.questions.insert(&mut newquestion);

// Encrypt the question in the divinity notes
let divinity_pub_key = get_public_key(divinity_address);
let questionsstorage_slot = storage.questions.storage_slot;

let encrypted = newquestion.serialize();

emit_encrypted_log(
&mut context,
address_this,
questionsstorage_slot,
divinity_pub_key,
encrypted,
);
}


// Oracle submit an answer to a given question
#[aztec(private)]
fn submit_answer(question: Field, answer: Field) {
let storage = Storage::init(Context::private(&mut context));
let caller = context.msg_sender();

// Filter request note to check if existing (either wrong request or already answered) and accesible for this divinity (should't happen)
let request_filter = NoteGetterOptions::new()
.select(0, question)
.select(3, caller)
.set_limit(1);

// Get the question note
let request = storage.questions.get_notes(request_filter)[0].unwrap_unchecked();

assert(request.request == question);
assert(request.divinity_address == caller);

let mut answer_to_keep = 0;

// Check if this question was previously answered
let answerFilter = NoteGetterOptions::new()
.select(0, question)
.set_limit(1);


let answerNote = storage.answers.get_notes(answerFilter)[0].unwrap_unchecked();

// TODO: this check might not be hit (fail before) if the note is uninit -> filter fn using is_some()?
// This question has already been answered?
if answerNote.answer != 0 {
assert(answerNote.request == question);
assert(answerNote.owner == caller);

// Update the answer to the preexisting one
answer_to_keep = answerNote.answer;
// If not, use the answer passed as arg
} else {
// Create a new note
answer_to_keep = answer;
}

// Store the answer in both sets (requester and oracle)
let mut new_answer: AnswerNote = AnswerNote::new(
question,
answer,
caller
);

storage.answers.insert(&mut new_answer);

// Encrypt the answer in the requester notes
let requester_pub_key = get_public_key(request.requester_address);
let answers_storage_slot = storage.answers.storage_slot;

let encrypted = new_answer.serialize();

emit_encrypted_log(
&mut context,
caller,
answers_storage_slot,
requester_pub_key,
encrypted,
);
}

// Requester consults the answer to one of their questions
#[aztec(private)]
fn consult_answer(question: Field) -> Field {

let storage = Storage::init(Context::private(&mut context));
let caller = context.msg_sender();

// create the answer_getter_option: select answer and limit
let answer_getter_option = NoteGetterOptions::new().select(0, question).select(2, caller).set_limit(1);

// filter the notes - get_notes returns the note **that the account has access to** (if not, add it to the Option)
let answered_note = storage.answers.get_notes(answer_getter_option)[0].unwrap_unchecked();

// constrain (owner == caller and _qeustion==note.request)
assert(answered_note.owner == caller);
assert(answered_note.request == question);

// Return the answer
answered_note.answer
}

// Requester cancels a question - no real utility here, will be usefull when request bound an amount, waiting for the answer
#[aztec(private)]
fn cancel_question(question: Field) {
let storage = Storage::init(Context::private(&mut context));
let caller = context.msg_sender();

// create the Answer_getter_option: SELECT by address and max number of requests
let question_getter_option = NoteGetterOptions::new().select(0, question).select(1, caller).set_limit(1);
let question_note = storage.questions.get_notes(question_getter_option)[0].unwrap_unchecked();

// Assert that the question exists and is owned by the caller
assert(question_note.request == question);
assert(question_note.requester_address == caller);

// nullify the note
storage.questions.remove(question_note);
}
}
2 changes: 2 additions & 0 deletions src/types.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod question_note;
mod answer_note;
93 changes: 93 additions & 0 deletions src/types/answer_note.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use dep::aztec::note::{
note_header::NoteHeader,
note_interface::NoteInterface,
utils::compute_note_hash_for_read_or_nullify,
};
use dep::aztec::oracle::{
get_secret_key::get_secret_key,
get_public_key::get_public_key,
};

struct AnswerNote {
request: Field,
answer: Field,
owner: Field, // Owner of this answer, either the divinity or the requester
header: NoteHeader
}

global ANSWER_NOTE_LEN: Field = 3;

impl AnswerNote {
fn new(_request: Field, _answer: Field, _owner: Field) -> Self {
AnswerNote {
request: _request,
answer: _answer,
owner: _owner,
header: NoteHeader::empty(),
}
}

fn serialize(self) -> [Field; ANSWER_NOTE_LEN] {
[self.request, self.answer, self.owner]
}

fn deserialize(preimage: [Field; ANSWER_NOTE_LEN]) -> Self {
AnswerNote {
request: preimage[0],
answer: preimage[1],
owner: preimage[2],
header: NoteHeader::empty(),
}
}

fn compute_note_hash(self) -> Field {
dep::std::hash::pedersen([
self.request,
self.answer,
self.owner
])[0]
}

// TODO: refactor to a set of immutable singleton instead
// No nullifier needed -> only one divinity has access to the original request and the answer cannot change ever
fn compute_nullifier(self) -> Field {
0
}

fn set_header(&mut self, header: NoteHeader) {
self.header = header;
}
}

fn deserialize(preimage: [Field; ANSWER_NOTE_LEN]) -> AnswerNote {
AnswerNote::deserialize(preimage)
}

fn serialize(note: AnswerNote) -> [Field; ANSWER_NOTE_LEN] {
note.serialize()
}

fn compute_note_hash(note: AnswerNote) -> Field {
note.compute_note_hash()
}

fn compute_nullifier(note: AnswerNote) -> Field {
note.compute_nullifier()
}

fn get_header(note: AnswerNote) -> NoteHeader {
note.header
}

fn set_header(note: &mut AnswerNote, header: NoteHeader) {
note.set_header(header)
}

global AnswerNoteMethods = NoteInterface {
deserialize,
serialize,
compute_note_hash,
compute_nullifier,
get_header,
set_header,
};
Loading

0 comments on commit afe8cb4

Please sign in to comment.