diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 6d41afd7ad8..561e62c6080 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -273,6 +273,9 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { bytecode } +/// Handle foreign function calls +/// - Environment getting opcodes will be represented as foreign calls +/// - TODO: support for avm external calls through this function fn handle_foreign_call( avm_instrs: &mut Vec, function: &String, @@ -301,8 +304,7 @@ fn handle_foreign_call( "version" => AvmOpcode::VERSION, "blockNumber" => AvmOpcode::BLOCKNUMBER, "timestamp" => AvmOpcode::TIMESTAMP, - // "isStaticCall" => AvmOpcode::ISSTATICCALL, - // "isDelegateCall" => AvmOpcode::ISDELEGATECALL, + // "callStackDepth" => AvmOpcode::CallStackDepth, _ => panic!( "Transpiler doesn't know how to process ForeignCall function {:?}", function diff --git a/barretenberg/cpp/pil/avm/avm.pil b/barretenberg/cpp/pil/avm/avm.pil new file mode 100644 index 00000000000..26268fe25cd --- /dev/null +++ b/barretenberg/cpp/pil/avm/avm.pil @@ -0,0 +1,191 @@ + +include "mem_trace.pil"; +include "alu_chip.pil"; + +namespace avmMini(256); + + //===== CONSTANT POLYNOMIALS ================================================== + pol constant clk(i) { i }; + pol constant first = [1] + [0]*; // Used mostly to toggle off the first row consisting + // only in first element of shifted polynomials. + + //===== CONTROL FLOW ========================================================== + // Program counter + pol commit pc; + // Return Pointer + pol commit internal_return_ptr; + + pol commit sel_internal_call; + pol commit sel_internal_return; + pol commit sel_jump; + + // Halt program execution + pol commit sel_halt; + + //===== TABLE SUBOP-TR ======================================================== + // Boolean selectors for (sub-)operations. Only one operation is activated at + // a time. + + // ADD + pol commit sel_op_add; + // SUB + pol commit sel_op_sub; + // MUL + pol commit sel_op_mul; + // DIV + pol commit sel_op_div; + + // Instruction memory tag (0: uninitialized, 1: u8, 2: u16, 3: u32, 4: u64, 5: u128, 6:field) + pol commit in_tag; + + // Errors + pol commit op_err; // Boolean flag pertaining to an operation error + pol commit tag_err; // Boolean flag (foreign key to memTrace.m_tag_err) + + // A helper witness being the inverse of some value + // to show a non-zero equality + pol commit inv; + + // Intermediate register values + pol commit ia; + pol commit ib; + pol commit ic; + + // Memory operation per intermediate register + pol commit mem_op_a; + pol commit mem_op_b; + pol commit mem_op_c; + + // Read-write flag per intermediate register: Read = 0, Write = 1 + pol commit rwa; + pol commit rwb; + pol commit rwc; + + // Memory index involved into a memory operation per pertaining intermediate register + // We should range constrain it to 32 bits ultimately. For first mini-AVM, + // we will assume that these columns are of the right type. + pol commit mem_idx_a; + pol commit mem_idx_b; + pol commit mem_idx_c; + + + // Track the last line of the execution trace. It does NOT correspond to the last row of the whole table + // of size N. As this depends on the supplied bytecode, this polynomial cannot be constant. + pol commit last; + + // Relations on type constraints + + sel_op_add * (1 - sel_op_add) = 0; + sel_op_sub * (1 - sel_op_sub) = 0; + sel_op_mul * (1 - sel_op_mul) = 0; + sel_op_div * (1 - sel_op_div) = 0; + + sel_internal_call * (1 - sel_internal_call) = 0; + sel_internal_return * (1 - sel_internal_return) = 0; + sel_jump * (1 - sel_jump) = 0; + sel_halt * (1 - sel_halt) = 0; + + op_err * (1 - op_err) = 0; + tag_err * (1 - tag_err) = 0; // Potential optimization (boolean constraint derivation from equivalence check to memTrace)? + + mem_op_a * (1 - mem_op_a) = 0; + mem_op_b * (1 - mem_op_b) = 0; + mem_op_c * (1 - mem_op_c) = 0; + + rwa * (1 - rwa) = 0; + rwb * (1 - rwb) = 0; + rwc * (1 - rwc) = 0; + + // TODO: Constrain rwa, rwb, rwc to u32 type and 0 <= in_tag <= 6 + + // Set intermediate registers to 0 whenever tag_err occurs + tag_err * ia = 0; + tag_err * ib = 0; + tag_err * ic = 0; + + // Relation for division over the finite field + // If tag_err == 1 in a division, then ib == 0 and op_err == 1. + #[SUBOP_DIVISION_FF] + sel_op_div * (1 - op_err) * (ic * ib - ia) = 0; + + // When sel_op_div == 1, we want ib == 0 <==> op_err == 1 + // This can be achieved with the 2 following relations. + // inv is an extra witness to show that we can invert ib, i.e., inv = ib^(-1) + // If ib == 0, we have to set inv = 1 to satisfy the second relation, + // because op_err == 1 from the first relation. + #[SUBOP_DIVISION_ZERO_ERR1] + sel_op_div * (ib * inv - 1 + op_err) = 0; + #[SUBOP_DIVISION_ZERO_ERR2] + sel_op_div * op_err * (1 - inv) = 0; + + // op_err cannot be maliciously activated for a non-relevant + // operation selector, i.e., op_err == 1 ==> sel_op_div || sel_op_XXX || ... + // op_err * (sel_op_div + sel_op_XXX + ... - 1) == 0 + // Note that the above is even a stronger constraint, as it shows + // that exactly one sel_op_XXX must be true. + // At this time, we have only division producing an error. + #[SUBOP_ERROR_RELEVANT_OP] + op_err * (sel_op_div - 1) = 0; + + // TODO: constraint that we stop execution at the first error (tag_err or op_err) + // An error can only happen at the last sub-operation row. + + // OPEN/POTENTIAL OPTIMIZATION: Dedicated error per relevant operation? + // For the division, we could lower the degree from 4 to 3 + // (sel_op_div - op_div_err) * (ic * ib - ia) = 0; + // Same for the relations related to the error activation: + // (ib * inv - 1 + op_div_err) = 0 && op_err * (1 - inv) = 0 + // This works in combination with op_div_err * (sel_op_div - 1) = 0; + // Drawback is the need to paralllelize the latter. + + //===== CONTROL FLOW ======================================================= + //===== JUMP =============================================================== + sel_jump * (pc' - ia) = 0; + + //===== INTERNAL_CALL ====================================================== + // - The program counter in the next row should be equal to the value loaded from the ia register + // - We then write the return location (pc + 1) into the call stack (in memory) + + #[RETURN_POINTER_INCREMENT] + sel_internal_call * (internal_return_ptr' - (internal_return_ptr + 1)) = 0; + sel_internal_call * (internal_return_ptr - mem_idx_b) = 0; + sel_internal_call * (pc' - ia) = 0; + sel_internal_call * ((pc + 1) - ib) = 0; + + // TODO(md): Below relations may be removed through sub-op table lookup + sel_internal_call * (rwb - 1) = 0; + sel_internal_call * (mem_op_b - 1) = 0; + + //===== INTERNAL_RETURN =================================================== + // - We load the memory pointer to be the internal_return_ptr + // - Constrain then next program counter to be the loaded value + // - decrement the internal_return_ptr + + #[RETURN_POINTER_DECREMENT] + sel_internal_return * (internal_return_ptr' - (internal_return_ptr - 1)) = 0; + sel_internal_return * ((internal_return_ptr - 1) - mem_idx_a) = 0; + sel_internal_return * (pc' - ia) = 0; + + // TODO(md): Below relations may be removed through sub-op table lookup + sel_internal_return * rwa = 0; + sel_internal_return * (mem_op_a - 1) = 0; + + //===== CONTROL_FLOW_CONSISTENCY ============================================ + pol INTERNAL_CALL_STACK_SELECTORS = (first + sel_internal_call + sel_internal_return + sel_halt); + pol OPCODE_SELECTORS = (sel_op_add + sel_op_sub + sel_op_div + sel_op_mul); + + // Program counter must increment if not jumping or returning + #[PC_INCREMENT] + (1 - first) * (1 - sel_halt) * OPCODE_SELECTORS * (pc' - (pc + 1)) = 0; + + // first == 0 && sel_internal_call == 0 && sel_internal_return == 0 && sel_halt == 0 ==> internal_return_ptr == internal_return_ptr' + #[INTERNAL_RETURN_POINTER_CONSISTENCY] + (1 - INTERNAL_CALL_STACK_SELECTORS) * (internal_return_ptr' - internal_return_ptr) = 0; + + // TODO: we want to set an initial number for the reserved memory of the jump pointer + + // Inter-table Constraints + + // TODO: tag_err {clk} IS memTrace.m_tag_err {memTrace.m_clk} + // TODO: Map memory trace with intermediate register values whenever there is no tag error, sthg like: + // mem_op_a * (1 - tag_err) {mem_idx_a, clk, ia, rwa} IS m_sub_clk == 0 && 1 - m_tag_err {m_addr, m_clk, m_val, m_rw} diff --git a/yarn-project/acir-simulator/src/avm/index.test.ts b/yarn-project/acir-simulator/src/avm/index.test.ts index e15e846927e..04ad3d5bbda 100644 --- a/yarn-project/acir-simulator/src/avm/index.test.ts +++ b/yarn-project/acir-simulator/src/avm/index.test.ts @@ -7,7 +7,7 @@ import { MockProxy, mock } from 'jest-mock-extended'; import { AvmMachineState } from './avm_machine_state.js'; import { TypeTag } from './avm_memory_types.js'; -import { initExecutionEnvironment } from './fixtures/index.js'; +import { initExecutionEnvironment, initGlobalVariables } from './fixtures/index.js'; import { executeAvm } from './interpreter/interpreter.js'; import { AvmJournal } from './journal/journal.js'; import { Add, CalldataCopy, Return } from './opcodes/index.js'; @@ -68,14 +68,21 @@ describe('avm', () => { }); describe('Test env getters from noir contract', () => { - const testEnvGetter = async (valueName: string, value: any, functionName: string) => { + const testEnvGetter = async (valueName: string, value: any, functionName: string, globalVar: boolean = false) => { const getterArtifact = AvmTestContractArtifact.functions.find(f => f.name === functionName); // Decode const instructions = decodeFromBytecode(Buffer.from(getterArtifact!.bytecode, 'base64')); // Execute - const overrides = { [valueName]: value }; + let overrides = {}; + if (globalVar === true) { + const globals = initGlobalVariables({ [valueName]: value }); + overrides = { globals }; + } else { + overrides = { [valueName]: value }; + } + const context = new AvmMachineState(initExecutionEnvironment(overrides)); const avmReturnData = await executeAvm(context, journal, instructions); @@ -126,25 +133,25 @@ describe('avm', () => { await testEnvGetter('feePerDaGas', fee, 'avm_getFeePerDaGas'); }); - // it('chainId', async () => { - // const chainId = new Fr(1); - // await testEnvGetter('chainId', chainId, 'avm_getChainId'); - // }); - - // it('version', async () => { - // const version = new Fr(1); - // await testEnvGetter('version', version, 'avm_getVersion'); - // }); - - // it('blockNumber', async () => { - // const blockNumber = new Fr(1); - // await testEnvGetter('blockNumber', blockNumber, 'avm_getBlockNumber'); - // }); - - // it('timestamp', async () => { - // const timestamp = new Fr(1); - // await testEnvGetter('timestamp', timestamp, 'avm_getTimestamp'); - // }); + it('chainId', async () => { + const chainId = new Fr(1); + await testEnvGetter('chainId', chainId, 'avm_getChainId', /*globalVar=*/ true); + }); + + it('version', async () => { + const version = new Fr(1); + await testEnvGetter('version', version, 'avm_getVersion', /*globalVar=*/ true); + }); + + it('blockNumber', async () => { + const blockNumber = new Fr(1); + await testEnvGetter('blockNumber', blockNumber, 'avm_getBlockNumber', /*globalVar=*/ true); + }); + + it('timestamp', async () => { + const timestamp = new Fr(1); + await testEnvGetter('timestamp', timestamp, 'avm_getTimestamp', /*globalVar=*/ true); + }); }); }); }); diff --git a/yarn-project/aztec-nr/aztec/src/avm.nr b/yarn-project/aztec-nr/aztec/src/avm.nr index 42deea96335..3d9885db80d 100644 --- a/yarn-project/aztec-nr/aztec/src/avm.nr +++ b/yarn-project/aztec-nr/aztec/src/avm.nr @@ -1,2 +1 @@ mod context; -mod env_getters; diff --git a/yarn-project/aztec-nr/aztec/src/avm/context.nr b/yarn-project/aztec-nr/aztec/src/avm/context.nr index 29011a90986..4e6d3f56f21 100644 --- a/yarn-project/aztec-nr/aztec/src/avm/context.nr +++ b/yarn-project/aztec-nr/aztec/src/avm/context.nr @@ -1,20 +1,3 @@ -use crate::avm::env_getters::{ - get_address, - get_storage_address, - get_origin, - get_sender, - get_portal, - get_fee_per_l1_gas, - get_fee_per_l2_gas, - get_fee_per_da_gas, - get_chain_id, - get_version, - get_block_number, - get_timestamp, - get_is_static_call, - get_is_delegate_call, -}; - use dep::protocol_types::address::{ AztecAddress, EthAddress, @@ -26,63 +9,42 @@ struct AvmContext {} // No new function as this struct is entirely static getters impl AvmContext { - pub fn address() -> AztecAddress { - get_address() - } - - pub fn storage_address() -> AztecAddress { - get_storage_address() - } - - pub fn origin() -> AztecAddress { - get_origin() - } + #[oracle(address)] + pub fn address() -> AztecAddress {} - pub fn sender() -> AztecAddress { - get_sender() - } + #[oracle(storageAddress)] + pub fn storage_address() -> AztecAddress {} - pub fn portal() -> EthAddress { - get_portal() - } + #[oracle(origin)] + pub fn origin() -> AztecAddress {} - pub fn fee_per_l1_gas() -> Field { - get_fee_per_l1_gas() - } + #[oracle(sender)] + pub fn sender() -> AztecAddress {} - pub fn fee_per_l2_gas() -> Field { - get_fee_per_l2_gas() - } + #[oracle(portal)] + pub fn portal() -> EthAddress {} - pub fn fee_per_da_gas() -> Field { - get_fee_per_da_gas() - } + #[oracle(feePerL1Gas)] + pub fn fee_per_l1_gas() -> Field {} - pub fn chain_id() -> Field { - get_chain_id() - } + #[oracle(feePerL2Gas)] + pub fn fee_per_l2_gas() -> Field {} - pub fn version() -> Field { - get_version() - } + #[oracle(feePerDaGas)] + pub fn fee_per_da_gas() -> Field {} - pub fn block_number() -> Field { - get_block_number() - } + #[oracle(chainId)] + pub fn chain_id() -> Field {} - pub fn timestamp() -> Field { - get_timestamp() - } + #[oracle(version)] + pub fn version() -> Field {} - // pub fn is_static_call() -> Field { - // get_is_static_call() - // } + #[oracle(blockNumber)] + pub fn block_number() -> Field {} - // pub fn is_delegate_call() -> Field { - // get_is_delegate_call() - // } + #[oracle(timestamp)] + pub fn timestamp() -> Field {} - // pub fn contract_call_depth() -> Field { - // get_contract_call_depth() - // } + // #[oracle(contractCallDepth)] + // pub fn contract_call_depth() -> Field {} } diff --git a/yarn-project/aztec-nr/aztec/src/avm/env_getters.nr b/yarn-project/aztec-nr/aztec/src/avm/env_getters.nr deleted file mode 100644 index 8d491e56723..00000000000 --- a/yarn-project/aztec-nr/aztec/src/avm/env_getters.nr +++ /dev/null @@ -1,49 +0,0 @@ -use dep::protocol_types::address::{ - AztecAddress, - EthAddress, -}; - -#[oracle(address)] -unconstrained pub fn get_address() -> AztecAddress {} - -#[oracle(storageAddress)] -unconstrained pub fn get_storage_address() -> AztecAddress {} - -#[oracle(origin)] -unconstrained pub fn get_origin() -> AztecAddress {} - -#[oracle(sender)] -unconstrained pub fn get_sender() -> AztecAddress {} - -#[oracle(portal)] -unconstrained pub fn get_portal() -> EthAddress {} - -#[oracle(feePerL1Gas)] -unconstrained pub fn get_fee_per_l1_gas() -> Field {} - -#[oracle(feePerL2Gas)] -unconstrained pub fn get_fee_per_l2_gas() -> Field {} - -#[oracle(feePerDaGas)] -unconstrained pub fn get_fee_per_da_gas() -> Field {} - -#[oracle(chainId)] -unconstrained pub fn get_chain_id() -> Field {} - -#[oracle(version)] -unconstrained pub fn get_version() -> Field {} - -#[oracle(blockNumber)] -unconstrained pub fn get_block_number() -> Field {} - -#[oracle(timestamp)] -unconstrained pub fn get_timestamp() -> Field {} - -#[oracle(isStaticCall)] -unconstrained pub fn get_is_static_call() -> Field {} - -#[oracle(isDelegateCall)] -unconstrained pub fn get_is_delegate_call() -> Field {} - -// #[oracle(contractCallDepth)] -// unconstrained pub fn get_contract_call_depth() -> Field {} \ No newline at end of file diff --git a/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr b/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr index 3b3400f173e..107b9494046 100644 --- a/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -28,17 +28,17 @@ contract AvmTest { } #[aztec(public-vm)] - fn getStorageAddress() -> pub AztecAddress { + fn getStorageAddress() -> pub AztecAddress { AvmContext::storage_address() } #[aztec(public-vm)] - fn getSender() -> pub AztecAddress { + fn getSender() -> pub AztecAddress { AvmContext::sender() } #[aztec(public-vm)] - fn getOrigin() -> pub AztecAddress { + fn getOrigin() -> pub AztecAddress { AvmContext::origin() } @@ -82,16 +82,6 @@ contract AvmTest { AvmContext::timestamp() } - // #[aztec(public-vm)] - // fn getIsStaticCall() -> pub Field { - // AvmContext::is_static_call() - // } - - // #[aztec(public-vm)] - // fn getIsDelegateCall() -> pub Field { - // AvmContext::is_delegate_call() - // } - // #[aztec(public-vm)] // fn getContractCallDepth() -> pub Field { // AvmContext::contract_call_depth()