Skip to content

Commit

Permalink
feat: add oracle for slow updates (#2878)
Browse files Browse the repository at this point in the history
Experimental, very much in progress.

Fixes #2852, building on top of #2732. 

For the experimentation here needed a name for my "data-stack" as it
could practically hold whatever you want to throw at it. Was listening
to https://www.youtube.com/watch?v=DTrNwbemNK0 while building so its the
pez dispenser. The PEZ dispenser is essentially a stack managed by the
PXE that hold `mint`s (PEZ Dispenser = "PfeffErminZ"/peppermint
dispenser). A `mint` is `Fr[]` which can hold whatever data really, here
just used to feed membership proofs and update proofs into the slow
updates map.
  • Loading branch information
LHerskind committed Oct 31, 2023
1 parent eb15a49 commit 42cf223
Show file tree
Hide file tree
Showing 15 changed files with 166 additions and 58 deletions.
6 changes: 6 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export class Oracle {
return witness.map(toACVMField);
}

async popMint(): Promise<ACVMField[]> {
const mint = await this.typedOracle.popMint();
if (!mint) throw new Error(`No mints available`);
return mint.map(toACVMField);
}

async getNotes(
[storageSlot]: ACVMField[],
[numSelects]: ACVMField[],
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export abstract class TypedOracle {
throw new Error('Not available.');
}

popMint(): Promise<Fr[]> {
throw new Error('Not available.');
}

getNotes(
_storageSlot: Fr,
_numSelects: number,
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/acir-simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export interface DBOracle extends CommitmentsDB {
*/
getAuthWitness(messageHash: Fr): Promise<Fr[]>;

/**
* Retrieve a mint from the pez dispenser.
* @returns A promise that resolves to an array of field elements representing the mint.
*/
popMint(): Promise<Fr[]>;

/**
* Retrieve the secret key associated with a specific public key.
* The function only allows access to the secret keys of the transaction creator,
Expand Down
11 changes: 10 additions & 1 deletion yarn-project/acir-simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ export class ViewDataOracle extends TypedOracle {
}

/**
* Gets some notes for a storage slot.
* Pops a mint from the PEZ dispenser
* @returns The mint values
*/
public popMint(): Promise<Fr[]> {
return this.db.popMint();
}

/**
* Gets some notes for a contract address and storage slot.
* Returns a flattened array containing real-note-count and note preimages.
*
* @remarks
* Check for pending notes with matching slot.
Expand Down
9 changes: 5 additions & 4 deletions yarn-project/aztec-nr/slow-updates-tree/src/slow_map.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ use dep::aztec::context::{PrivateContext, PublicContext, Context};
use dep::aztec::state_vars::public_state::PublicState;
use dep::aztec::types::type_serialization::TypeSerializationInterface;
use dep::aztec::oracle::storage::{storage_read, storage_write};
use dep::aztec::oracle::debug_log::debug_log_format;

use dep::std::hash::pedersen_hash;

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

// The epoch length is just a random number for now.
Expand Down Expand Up @@ -59,6 +57,10 @@ struct SlowUpdateProof<N, M> {
after: SlowUpdateInner<N>,
}

pub fn deserialize_slow_update_proof<N, M>(serialized: [Field; M]) -> SlowUpdateProof<N, M> {
SlowUpdateProof::deserialize(serialized)
}

impl<N, M> SlowUpdateProof<N, M> {
pub fn serialize(self: Self) -> pub [Field; M] {
let mut serialized = [0; M];
Expand Down Expand Up @@ -250,7 +252,6 @@ impl<N,M> SlowMap<N,M> {
let fields = root.serialize();
storage_write(self.storage_slot, fields);
}

}

pub fn compute_merkle_root<N>(leaf: Field, index: Field, hash_path: [Field; N]) -> Field {
Expand All @@ -267,4 +268,4 @@ pub fn compute_merkle_root<N>(leaf: Field, index: Field, hash_path: [Field; N])
current = pedersen_hash([hash_left, hash_right]);
};
current
}
}
4 changes: 4 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export abstract class BaseWallet implements Wallet {

abstract createAuthWitness(message: Fr): Promise<AuthWitness>;

addMint(mint: Fr[]): Promise<void> {
return this.pxe.addMint(mint);
}

registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise<CompleteAddress> {
return this.pxe.registerAccount(privKey, partialAddress);
}
Expand Down
48 changes: 32 additions & 16 deletions yarn-project/end-to-end/src/e2e_slow_tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CheatCodes, Fr, Wallet, sleep } from '@aztec/aztec.js';
import { CheatCodes, Fr, Wallet } from '@aztec/aztec.js';
import { CircuitsWasm } from '@aztec/circuits.js';
import { DebugLogger } from '@aztec/foundation/log';
import { Pedersen, SparseTree, newTree } from '@aztec/merkle-tree';
Expand Down Expand Up @@ -42,6 +42,10 @@ describe('e2e_slow_tree', () => {
};
};

const getMembershipMint = (proof: { index: bigint; value: Fr; sibling_path: Fr[] }) => {
return [new Fr(proof.index), proof.value, ...proof.sibling_path];
};

const getUpdateProof = async (newValue: bigint, index: bigint) => {
const beforeProof = await getMembershipProof(index, false);
const afterProof = await getMembershipProof(index, true);
Expand All @@ -57,6 +61,22 @@ describe('e2e_slow_tree', () => {
};
};

const getUpdateMint = (proof: {
index: bigint;
new_value: bigint;
before: { value: Fr; sibling_path: Fr[] };
after: { value: Fr; sibling_path: Fr[] };
}) => {
return [
new Fr(proof.index),
new Fr(proof.new_value),
proof.before.value,
...proof.before.sibling_path,
proof.after.value,
...proof.after.sibling_path,
];
};

const status = async (key: bigint) => {
logger(`\tTime: ${await cheatCodes.eth.timestamp()}`);
logger('\tRoot', await contract.methods.un_read_root(owner).view());
Expand All @@ -70,11 +90,8 @@ describe('e2e_slow_tree', () => {

logger('Initial state');
await status(key);

await contract.methods
.read_at({ ...(await getMembershipProof(key, true)), value: 0n })
.send()
.wait();
await wallet.addMint(getMembershipMint(await getMembershipProof(key, true)));
await contract.methods.read_at(key).send().wait();

logger(`Updating tree[${key}] to 1 from public`);
await contract.methods
Expand All @@ -86,7 +103,8 @@ describe('e2e_slow_tree', () => {

const zeroProof = await getMembershipProof(key, false);
logger(`"Reads" tree[${zeroProof.index}] from the tree, equal to ${zeroProof.value}`);
await contract.methods.read_at(zeroProof).send().wait();
await wallet.addMint(getMembershipMint({ ...zeroProof, value: new Fr(0) }));
await contract.methods.read_at(key).send().wait();

// Progress time to beyond the update and thereby commit it to the tree.
await cheatCodes.aztec.warp((await cheatCodes.eth.timestamp()) + 1000);
Expand All @@ -97,20 +115,18 @@ describe('e2e_slow_tree', () => {
logger(
`Tries to "read" tree[${zeroProof.index}] from the tree, but is rejected as value is not ${zeroProof.value}`,
);
await expect(contract.methods.read_at(zeroProof).simulate()).rejects.toThrowError(
await wallet.addMint(getMembershipMint({ ...zeroProof, value: new Fr(0) }));
await expect(contract.methods.read_at(key).simulate()).rejects.toThrowError(
'Assertion failed: Root does not match expected',
);

logger(`"Reads" tree[${key}], expect to be 1`);
await contract.methods
.read_at({ ...(await getMembershipProof(key, false)), value: 1n })
.send()
.wait();
await wallet.addMint(getMembershipMint({ ...zeroProof, value: new Fr(1) }));
await contract.methods.read_at(key).send().wait();

logger(`Updating tree[${key}] to 4 from private`);
await contract.methods
.update_at_private(await getUpdateProof(4n, key))
.send()
.wait();
await wallet.addMint(getUpdateMint(await getUpdateProof(4n, key)));
await contract.methods.update_at_private(key, 4n).send().wait();
await tree.updateLeaf(new Fr(4).toBuffer(), key);

await status(key);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod pez;
mod types;

// This contract allow us to "read" public storage in private through a delayed tree.
// More documentation need to be outlined for this properly, but there is some in
// https://github.com/AztecProtocol/aztec-packages/pull/2732/files
Expand All @@ -22,9 +25,12 @@ contract SlowTree {
},
};
use dep::slow_updates_tree::slow_map::{
SlowMap, Leaf, SlowUpdateProof, compute_merkle_root
SlowMap, Leaf, SlowUpdateProof, compute_merkle_root, deserialize_slow_update_proof
};

use crate::pez::pop_mint;
use crate::types::{MembershipProof, deserialize_membership_proof};

global TREE_HEIGHT: Field = 254;
global MEMBERSHIP_SIZE: Field = 256; // TREE_HEIGHT + 2
global UPDATE_SIZE: Field = 512; // TREE_HEIGHT * 2 + 4
Expand All @@ -33,39 +39,6 @@ contract SlowTree {
trees: Map<SlowMap<TREE_HEIGHT, UPDATE_SIZE>>,
}

// A single inclusion proof.
// M = N + 2
struct MembershipProof<N, M> {
index: Field,
value: Field,
sibling_path: [Field; N],

}

impl<N, M> MembershipProof<N, M> {
pub fn serialize(self: Self) -> pub [Field; M] {
let mut serialized = [0; M];
serialized[0] = self.index;
serialized[1] = self.value;
for i in 0..N {
serialized[2 + i] = self.sibling_path[i];
}
serialized
}

pub fn deserialize(serialized: [Field; M]) -> pub Self {
let mut sibling_path = [0; N];
for i in 0..N {
sibling_path[i] = serialized[2 + i];
}
MembershipProof {
index: serialized[0],
value: serialized[1],
sibling_path,
}
}
}

impl Storage {
fn init(context: Context) -> pub Self {
Storage {
Expand Down Expand Up @@ -102,7 +75,11 @@ contract SlowTree {
}

#[aztec(private)]
fn read_at(p: MembershipProof<TREE_HEIGHT, MEMBERSHIP_SIZE>) -> Field {
fn read_at(index: Field) -> Field {
let fields = pop_mint();
let p: MembershipProof<TREE_HEIGHT, MEMBERSHIP_SIZE> = deserialize_membership_proof(fields);
assert(index == p.index, "Index does not match expected");

let expected_root = compute_merkle_root(p.value, p.index, p.sibling_path);
let selector = compute_selector("_assert_current_root(Field,Field)");
context.call_public_function(
Expand All @@ -126,7 +103,12 @@ contract SlowTree {
}

#[aztec(private)]
fn update_at_private(p: SlowUpdateProof<TREE_HEIGHT, UPDATE_SIZE>) {
fn update_at_private(index: Field, new_value: Field) {
let fields = pop_mint();
let p: SlowUpdateProof<TREE_HEIGHT, UPDATE_SIZE> = deserialize_slow_update_proof(fields);
assert(index == p.index, "Index does not match expected");
assert(new_value == p.new_value, "New value does not match expected");

// We compute the root before.
let before_root = compute_merkle_root(p.before.value, p.index, p.before.sibling_path);
let after_root = compute_merkle_root(p.after.value, p.index, p.after.sibling_path);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[oracle(popMint)]
fn pop_mint_oracle<N>() -> [Field; N] {}

unconstrained pub fn pop_mint<N>() -> [Field; N] {
pop_mint_oracle()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// A single inclusion proof.
// M = N + 2
struct MembershipProof<N, M> {
index: Field,
value: Field,
sibling_path: [Field; N],
}

pub fn deserialize_membership_proof<N, M>(serialized: [Field; M]) -> pub MembershipProof<N, M> {
let mut sibling_path = [0; N];
for i in 0..N {
sibling_path[i] = serialized[2 + i];
}
MembershipProof {
index: serialized[0],
value: serialized[1],
sibling_path,
}
}


impl<N, M> MembershipProof<N, M> {
pub fn serialize(self: Self) -> pub [Field; M] {
let mut serialized = [0; M];
serialized[0] = self.index;
serialized[1] = self.value;
for i in 0..N {
serialized[2 + i] = self.sibling_path[i];
}
serialized
}

pub fn deserialize(serialized: [Field; M]) -> Self {
deserialize_membership_proof(serialized)
}
}
12 changes: 12 additions & 0 deletions yarn-project/pxe/src/database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ export interface Database extends ContractDatabase {
*/
getAuthWitness(messageHash: Fr): Promise<Fr[]>;

/**
* Adding a mint to the pez dispenser.
* @param mint - An array of field elements representing the mint.
*/
addMint(mint: Fr[]): Promise<void>;

/**
* Get the next mint from the pez dispenser.
* @returns A promise that resolves to an array of field elements representing the mint.
*/
popMint(): Promise<Fr[] | undefined>;

/**
* Gets notes based on the provided filter.
* @param filter - The filter to apply to the notes.
Expand Down
12 changes: 11 additions & 1 deletion yarn-project/pxe/src/database/memory_db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class MemoryDB extends MemoryContractDatabase implements Database {
private globalVariablesHash: Fr | undefined;
private addresses: CompleteAddress[] = [];
private authWitnesses: Record<string, Fr[]> = {};
private pezDispenser: Fr[][] = [];

constructor(logSuffix?: string) {
super(createDebugLogger(logSuffix ? 'aztec:memory_db_' + logSuffix : 'aztec:memory_db'));
Expand All @@ -44,11 +45,20 @@ export class MemoryDB extends MemoryContractDatabase implements Database {
return Promise.resolve(this.authWitnesses[messageHash.toString()]);
}

public addNote(note: NoteDao) {
public addNote(note: NoteDao): Promise<void> {
this.notesTable.push(note);
return Promise.resolve();
}

public addMint(mint: Fr[]): Promise<void> {
this.pezDispenser.push(mint);
return Promise.resolve();
}

public popMint(): Promise<Fr[] | undefined> {
return Promise.resolve(this.pezDispenser.pop());
}

public addNotes(notes: NoteDao[]) {
this.notesTable.push(...notes);
return Promise.resolve();
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export class PXEService implements PXE {
return this.db.addAuthWitness(witness.requestHash, witness.witness);
}

public addMint(mint: Fr[]) {
return this.db.addMint(mint);
}

public async registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise<CompleteAddress> {
const completeAddress = await CompleteAddress.fromPrivateKeyAndPartialAddress(privKey, partialAddress);
const wasAdded = await this.db.addCompleteAddress(completeAddress);
Expand Down
Loading

0 comments on commit 42cf223

Please sign in to comment.