Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aztec-noir): align public and private execution patterns #1515

Merged
merged 20 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions yarn-project/acir-simulator/src/acvm/deserialize.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import {
CallContext,
ContractDeploymentData,
ContractStorageRead,
ContractStorageUpdateRequest,
MAX_NEW_COMMITMENTS_PER_CALL,
MAX_NEW_L2_TO_L1_MSGS_PER_CALL,
MAX_NEW_NULLIFIERS_PER_CALL,
MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL,
MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL,
MAX_PUBLIC_DATA_READS_PER_CALL,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL,
MAX_READ_REQUESTS_PER_CALL,
NUM_FIELDS_PER_SHA256,
PrivateCircuitPublicInputs,
PublicCircuitPublicInputs,
RETURN_VALUES_LENGTH,
} from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { padArrayEnd } from '@aztec/foundation/collection';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr, Point } from '@aztec/foundation/fields';
import { Tuple } from '@aztec/foundation/serialize';

import { getReturnWitness } from 'acvm_js';

Expand Down Expand Up @@ -96,12 +103,12 @@ export class PublicInputsReader {
* @param length - The length of the array.
* @returns The array of fields.
*/
public readFieldArray(length: number): Fr[] {
public readFieldArray<N extends number>(length: N): Tuple<Fr, N> {
const array: Fr[] = [];
for (let i = 0; i < length; i++) {
array.push(this.readField());
}
return array;
return array as Tuple<Fr, N>;
}
}

Expand All @@ -111,7 +118,10 @@ export class PublicInputsReader {
* @param acir - The ACIR bytecode.
* @returns The public inputs.
*/
export function extractPublicInputs(partialWitness: ACVMWitness, acir: Buffer): PrivateCircuitPublicInputs {
export function extractPrivateCircuitPublicInputs(
partialWitness: ACVMWitness,
acir: Buffer,
): PrivateCircuitPublicInputs {
const witnessReader = new PublicInputsReader(partialWitness, acir);

const callContext = new CallContext(
Expand Down Expand Up @@ -184,3 +194,84 @@ export function extractPublicInputs(partialWitness: ACVMWitness, acir: Buffer):
version,
);
}

/**
* Extracts the public circuit public inputs from the ACVM generated partial witness.
* @param partialWitness - The partial witness.
* @param acir - The ACIR bytecode.
* @returns The public inputs.
*/
export function extractPublicCircuitPublicInputs(partialWitness: ACVMWitness, acir: Buffer): PublicCircuitPublicInputs {
const witnessReader = new PublicInputsReader(partialWitness, acir);

const callContext = new CallContext(
frToAztecAddress(witnessReader.readField()),
frToAztecAddress(witnessReader.readField()),
witnessReader.readField(),
frToBoolean(witnessReader.readField()),
frToBoolean(witnessReader.readField()),
frToBoolean(witnessReader.readField()),
);

const argsHash = witnessReader.readField();
const returnValues = padArrayEnd(witnessReader.readFieldArray(RETURN_VALUES_LENGTH), Fr.ZERO, RETURN_VALUES_LENGTH);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to pad it when reading array of length RETURN_VALUES_LENGTH? Won't you be filling RETURN_VALUES_LENGTH - RETURN_VALUES_LENGTH = 0 elements with Fr.ZERO values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is there as a sanity check, i can remove

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just seemed strange when no others had it.


const contractStorageUpdateRequests = new Array(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL).fill(
ContractStorageUpdateRequest.empty(),
);
for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL; i++) {
const request = new ContractStorageUpdateRequest(
witnessReader.readField(),
witnessReader.readField(),
witnessReader.readField(),
);
contractStorageUpdateRequests[i] = request;
}
const contractStorageReads = new Array(MAX_PUBLIC_DATA_READS_PER_CALL).fill(ContractStorageRead.empty());
for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL; i++) {
const request = new ContractStorageRead(witnessReader.readField(), witnessReader.readField());
contractStorageReads[i] = request;
}
// const contractStorageRead = witnessReader.readFieldArray(MAX_PUBLIC_DATA_READS_PER_CALL);

const publicCallStack = witnessReader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL);
const newCommitments = witnessReader.readFieldArray(MAX_NEW_COMMITMENTS_PER_CALL);
const newNullifiers = witnessReader.readFieldArray(MAX_NEW_NULLIFIERS_PER_CALL);
const newL2ToL1Msgs = witnessReader.readFieldArray(MAX_NEW_L2_TO_L1_MSGS_PER_CALL);

const unencryptedLogsHash = witnessReader.readFieldArray(NUM_FIELDS_PER_SHA256);
const unencryptedLogPreimagesLength = witnessReader.readField();

// const privateDataTreeRoot = witnessReader.readField();
// const nullifierTreeRoot = witnessReader.readField();
// const contractTreeRoot = witnessReader.readField();
// const l1Tol2TreeRoot = witnessReader.readField();
// const blocksTreeRoot = witnessReader.readField();
// const prevGlobalVariablesHash = witnessReader.readField();
// const publicDataTreeRoot = witnessReader.readField();
const historicPublicDataTreeRoot = witnessReader.readField();

const proverAddress = AztecAddress.fromField(witnessReader.readField());

// TODO(md): Should the global variables and stuff be included in here?

return new PublicCircuitPublicInputs(
callContext,
argsHash,
returnValues,
// TODO: how remove
contractStorageUpdateRequests as Tuple<
ContractStorageUpdateRequest,
typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL
>,
contractStorageReads as Tuple<ContractStorageRead, typeof MAX_PUBLIC_DATA_READS_PER_CALL>,
publicCallStack,
newCommitments,
newNullifiers,
newL2ToL1Msgs,
unencryptedLogsHash,
unencryptedLogPreimagesLength,
historicPublicDataTreeRoot,
proverAddress,
);
}
8 changes: 4 additions & 4 deletions yarn-project/acir-simulator/src/client/private_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { createDebugLogger } from '@aztec/foundation/log';
import { to2Fields } from '@aztec/foundation/serialize';
import { FunctionL2Logs, NotePreimage, NoteSpendingInfo } from '@aztec/types';

import { extractPublicInputs, frToAztecAddress, frToSelector } from '../acvm/deserialize.js';
import { extractPrivateCircuitPublicInputs, frToAztecAddress, frToSelector } from '../acvm/deserialize.js';
import {
ZERO_ACVM_FIELD,
acvm,
Expand Down Expand Up @@ -53,7 +53,7 @@ export class PrivateFunctionExecution {
this.log(`Executing external function ${this.contractAddress.toString()}:${selector}`);

const acir = Buffer.from(this.abi.bytecode, 'base64');
const initialWitness = this.writeInputs();
const initialWitness = this.getInitialWitness();

// TODO: Move to ClientTxExecutionContext.
const newNotePreimages: NewNoteData[] = [];
Expand Down Expand Up @@ -182,7 +182,7 @@ export class PrivateFunctionExecution {
},
});

const publicInputs = extractPublicInputs(partialWitness, acir);
const publicInputs = extractPrivateCircuitPublicInputs(partialWitness, acir);

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1165) --> set this in Noir
publicInputs.encryptedLogsHash = to2Fields(encryptedLogs.hash());
Expand Down Expand Up @@ -221,7 +221,7 @@ export class PrivateFunctionExecution {
* Writes the function inputs to the initial witness.
* @returns The initial witness.
*/
private writeInputs() {
private getInitialWitness() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bon Appetit Gourmet GIF by Studios 2016

const contractDeploymentData = this.context.txContext.contractDeploymentData ?? ContractDeploymentData.empty();

const blockData = this.context.constantHistoricBlockData;
Expand Down
6 changes: 4 additions & 2 deletions yarn-project/acir-simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
ZERO_ACVM_FIELD,
acvm,
convertACVMFieldToBuffer,
extractReturnWitness,
extractPublicCircuitPublicInputs,
frToAztecAddress,
frToSelector,
fromACVMField,
Expand Down Expand Up @@ -160,7 +160,9 @@ export class PublicExecutor {
},
});

const returnValues = extractReturnWitness(acir, partialWitness).map(fromACVMField);
// TODO: get the rest of everything from here, this should also be used to get the new Commitments, Nullifiers etc.
const publicInputs = extractPublicCircuitPublicInputs(partialWitness, acir);
const { returnValues } = publicInputs;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason you are not doing? Don't seems like you are using the publicInputs

     const { returnValues } = extractPublicCircuitPublicInputs(partialWitness, acir);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of the public inputs is dependant on some ongoing discussions around how we will handle context in the vm setting. I am unsure if we will use the publicInputs further. When i started the work I was under the impression that I was going to. I will update


const [contractStorageReads, contractStorageUpdateRequests] = storageActions.collect();

Expand Down
10 changes: 5 additions & 5 deletions yarn-project/acir-simulator/src/public/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('ACIR public execution simulator', () => {
const result = await executor.execute(execution, GlobalVariables.empty());

const expectedBalance = new Fr(160n);
expect(result.returnValues).toEqual([expectedBalance]);
expect(result.returnValues[0]).toEqual(expectedBalance);

const storageSlot = computeSlotForMapping(new Fr(1n), recipient.toField(), circuitsWasm);
expect(result.contractStorageUpdateRequests).toEqual([
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('ACIR public execution simulator', () => {
const expectedRecipientBalance = new Fr(160n);
const expectedSenderBalance = new Fr(60n);

expect(result.returnValues).toEqual([expectedRecipientBalance]);
expect(result.returnValues[0]).toEqual(expectedRecipientBalance);

expect(result.contractStorageUpdateRequests).toEqual([
{ storageSlot: senderStorageSlot, oldValue: senderBalance, newValue: expectedSenderBalance },
Expand All @@ -174,7 +174,7 @@ describe('ACIR public execution simulator', () => {

const result = await executor.execute(execution, GlobalVariables.empty());

expect(result.returnValues).toEqual([recipientBalance]);
expect(result.returnValues[0]).toEqual(recipientBalance);

expect(result.contractStorageReads).toEqual([
{ storageSlot: recipientStorageSlot, value: recipientBalance },
Expand Down Expand Up @@ -245,15 +245,15 @@ describe('ACIR public execution simulator', () => {
} else {
const result = await executor.execute(execution, globalVariables);

expect(result.returnValues).toEqual([
expect(result.returnValues[0]).toEqual(
new Fr(
initialValue +
globalVariables.chainId.value +
globalVariables.version.value +
globalVariables.blockNumber.value +
globalVariables.timestamp.value,
),
]);
);
}
},
20_000,
Expand Down
9 changes: 9 additions & 0 deletions yarn-project/foundation/src/aztec-address/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ export class AztecAddress {
return `${str.slice(0, 10)}...${str.slice(-4)}`;
}

/**
* Returns this address from a Field element.
* @param field - The Field element to convert.
* @returns An Address Object from a Field element with the same value.
*/
static fromField(field: Fr): AztecAddress {
return new AztecAddress(toBufferBE(field.value, AztecAddress.SIZE_IN_BYTES));
}

/**
* Returns this address as a field element.
* @returns A field element with the same value as the address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,58 @@ contract Child {
use dep::aztec::abi;
use dep::aztec::abi::PrivateContextInputs;
use dep::aztec::abi::PublicContextInputs;
use dep::aztec::context::Context;
use dep::aztec::context::{
PrivateContext,
PublicContext
};
use crate::storage::Storage;

fn constructor(
inputs: PrivateContextInputs,
) -> distinct pub abi::PrivateCircuitPublicInputs {
// Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.
Context::new(inputs, 0).finish()
PrivateContext::new(inputs, 0).finish()
}

// Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values.
fn value(
inputs: PrivateContextInputs,
input: Field,
) -> distinct pub abi::PrivateCircuitPublicInputs {
let mut context = Context::new(inputs, abi::hash_args([input]));
let mut context = PrivateContext::new(inputs, abi::hash_args([input]));

context.return_values.push(input + inputs.private_global_variables.chain_id + inputs.private_global_variables.version);
context.return_values.push(input + context.chain_id() + context.version());

// Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel.
context.finish()
}

// Returns base_value + 42.
open fn pubValue(inputs: PublicContextInputs, base_value: Field) -> pub Field {
base_value + inputs.public_global_variables.chain_id + inputs.public_global_variables.version + inputs.public_global_variables.block_number + inputs.public_global_variables.timestamp
open fn pubValue(inputs: PublicContextInputs, base_value: Field) -> pub abi::PublicCircuitPublicInputs {
let mut context = PublicContext::new(inputs, abi::hash_args([base_value]));

// TODO: make these available on context
let returnValue = base_value + context.chain_id() + context.version() + context.block_number() + context.timestamp();

context.return_values.push(returnValue);
// TODO(MADDIAA): MAYBE we put the return values inside the finish object? That could have nice UX
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think return_values are part of the PublicCircuitPublicInputs, although I think there are plans to instead return a return_values_hash. When that change happens, the simulator will keep track of the return_values, and the simulator could return those.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but it's true that even if we do return_values_hash we don't need to keep them inside the context, they can instead be passed as arguments to finish() both in public and private, and when we have return_values_hash then finish(hash_returns(whatever))

context.finish()
}

// Increments `current_value` by `new_value` and returns `new_value` + 1.
open fn pubStoreValue(_inputs: PublicContextInputs, new_value: Field) -> pub Field {
open fn pubStoreValue(inputs: PublicContextInputs, new_value: Field) -> pub abi::PublicCircuitPublicInputs {
let mut context = PublicContext::new(inputs, abi::hash_args([new_value]));

let storage = Storage::init();
let old_value = storage.current_value.read();
// Compiler complains if we don't assign the result to anything
let _void1 = storage.current_value.write(old_value + new_value);
// Compiler fails with "we do not allow private ABI inputs to be returned as public outputs" if we try to
// return new_value as-is, but then it also complains if we add `pub` to `new_value` in the args, so we
// just assign it to another variable and tweak it so it's not the same value, and the compiler is happy.
let ret_value = new_value + 1;
ret_value
let return_value = new_value + 1;

context.return_values.push(return_value);
context.finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ contract EasyPrivateToken {
use dep::aztec::{
abi,
abi::PrivateContextInputs,
context::Context,
context::PrivateContext,
log::emit_unencrypted_log,
note::{
note_header::NoteHeader,
Expand All @@ -33,7 +33,7 @@ contract EasyPrivateToken {
initial_supply: u120,
owner: Field,
) -> distinct pub abi::PrivateCircuitPublicInputs {
let mut context = Context::new(inputs, abi::hash_args([initial_supply as Field, owner]));
let mut context = PrivateContext::new(inputs, abi::hash_args([initial_supply as Field, owner]));
let storage = Storage::init();
let balances = storage.balances;

Expand All @@ -54,7 +54,7 @@ contract EasyPrivateToken {
amount: u120,
owner: Field,
) -> distinct pub abi::PrivateCircuitPublicInputs {
let mut context = Context::new(inputs, abi::hash_args([amount as Field, owner]));
let mut context = PrivateContext::new(inputs, abi::hash_args([amount as Field, owner]));
let storage = Storage::init();
let balances = storage.balances;

Expand All @@ -76,7 +76,7 @@ contract EasyPrivateToken {
sender: Field,
recipient: Field,
) -> distinct pub abi::PrivateCircuitPublicInputs {
let mut context = Context::new(inputs, abi::hash_args([amount as Field, sender, recipient]));
let mut context = PrivateContext::new(inputs, abi::hash_args([amount as Field, sender, recipient]));
let storage = Storage::init();
let balances = storage.balances;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract EcdsaAccount {
use dep::aztec::abi;
use dep::aztec::abi::PrivateContextInputs;
use dep::aztec::abi::CallContext;
use dep::aztec::context::Context;
use dep::aztec::context::PrivateContext;
use dep::aztec::log::emit_encrypted_log;
use dep::aztec::oracle::get_public_key::get_public_key;
use dep::aztec::types::vec::BoundedVec;
Expand Down Expand Up @@ -40,7 +40,7 @@ contract EcdsaAccount {
let mut args: BoundedVec<Field, 77> = BoundedVec::new(0);
args.push_array(payload.serialize());
for byte in signature { args.push(byte as Field); }
let mut context = Context::new(inputs, abi::hash_args(args.storage));
let mut context = PrivateContext::new(inputs, abi::hash_args(args.storage));

// Load public key from storage
let storage = Storage::init();
Expand Down Expand Up @@ -71,10 +71,10 @@ contract EcdsaAccount {
let mut args: BoundedVec<Field, 64> = BoundedVec::new(0);
for byte in signing_pub_key_x { args.push(byte as Field); }
for byte in signing_pub_key_y { args.push(byte as Field); }
let mut context = Context::new(inputs, abi::hash_args(args.storage));
let mut context = PrivateContext::new(inputs, abi::hash_args(args.storage));

let this = inputs.call_context.storage_contract_address;
let mut pub_key_note = EcdsaPublicKeyNote::new(signing_pub_key_x, signing_pub_key_y, inputs.call_context.storage_contract_address);
let this = context.this_address();
let mut pub_key_note = EcdsaPublicKeyNote::new(signing_pub_key_x, signing_pub_key_y, this);
storage.public_key.initialise(&mut context, &mut pub_key_note);

emit_encrypted_log(
Expand Down
Loading