1+ #include " AvmMini_execution.hpp"
2+ #include " barretenberg/common/serialize.hpp"
3+ #include " barretenberg/proof_system/circuit_builder/generated/AvmMini_circuit_builder.hpp"
4+ #include " barretenberg/vm/avm_trace/AvmMini_common.hpp"
5+ #include " barretenberg/vm/avm_trace/AvmMini_instructions.hpp"
6+ #include " barretenberg/vm/avm_trace/AvmMini_opcode.hpp"
7+ #include " barretenberg/vm/avm_trace/AvmMini_trace.hpp"
8+ #include " barretenberg/vm/generated/AvmMini_composer.hpp"
9+ #include < cstddef>
10+ #include < cstdint>
11+ #include < string>
12+ #include < vector>
13+
14+ namespace avm_trace {
15+
16+ /* *
17+ * @brief Run the bytecode, generate the corresponding execution trace and prove the correctness
18+ * of the execution of the supplied bytecode.
19+ *
20+ * @param bytecode A vector of bytes representing the bytecode to execute.
21+ * @param calldata expressed as a vector of finite field elements.
22+ * @throws runtime_error exception when the bytecode is invalid.
23+ * @return A zk proof of the execution.
24+ */
25+ plonk::proof Execution::run_and_prove (std::vector<uint8_t > const & bytecode, std::vector<FF> const & calldata)
26+ {
27+ auto instructions = parse (bytecode);
28+ auto trace = gen_trace (instructions, calldata);
29+ auto circuit_builder = bb::AvmMiniCircuitBuilder ();
30+ circuit_builder.set_trace (std::move (trace));
31+
32+ auto composer = bb::honk::AvmMiniComposer ();
33+ auto prover = composer.create_prover (circuit_builder);
34+ return prover.construct_proof ();
35+ }
36+
37+ /* *
38+ * @brief Parsing of the supplied bytecode into a vector of instructions. It essentially
39+ * checks that each opcode value is in the defined range and extracts the operands
40+ * for each opcode.
41+ *
42+ * @param bytecode The bytecode to be parsed as a vector of bytes/uint8_t
43+ * @throws runtime_error exception when the bytecode is invalid.
44+ * @return Vector of instructions
45+ */
46+ std::vector<Instruction> Execution::parse (std::vector<uint8_t > const & bytecode)
47+ {
48+ std::vector<Instruction> instructions;
49+ size_t pos = 0 ;
50+ const auto length = bytecode.size ();
51+
52+ while (pos < length) {
53+ const uint8_t opcode_byte = bytecode.at (pos);
54+ pos += AVM_OPCODE_BYTE_LENGTH;
55+
56+ if (!Bytecode::is_valid (opcode_byte)) {
57+ throw std::runtime_error (" Invalid opcode byte: " + std::to_string (opcode_byte));
58+ }
59+
60+ const auto opcode = static_cast <OpCode>(opcode_byte);
61+ auto in_tag_u8 = static_cast <uint8_t >(AvmMemoryTag::U0);
62+
63+ if (Bytecode::has_in_tag (opcode)) {
64+ if (pos + AVM_IN_TAG_BYTE_LENGTH > length) {
65+ throw std::runtime_error (" Instruction tag missing at position " + std::to_string (pos));
66+ }
67+ in_tag_u8 = bytecode.at (pos);
68+ if (in_tag_u8 == static_cast <uint8_t >(AvmMemoryTag::U0) || in_tag_u8 > MAX_MEM_TAG) {
69+ throw std::runtime_error (" Instruction tag is invalid at position " + std::to_string (pos) +
70+ " value: " + std::to_string (in_tag_u8));
71+ }
72+ pos += AVM_IN_TAG_BYTE_LENGTH;
73+ }
74+
75+ auto const in_tag = static_cast <AvmMemoryTag>(in_tag_u8);
76+ std::vector<uint32_t > operands{};
77+ size_t num_of_operands{};
78+ size_t operands_size{};
79+
80+ // SET opcode particularity about the number of operands depending on the
81+ // instruction tag. Namely, a constant of type instruction tag and not a
82+ // memory address is passed in the operands.
83+ // The bytecode of the operands is of the form CONSTANT || dst_offset
84+ // CONSTANT is of size k bits for type Uk, k=8,16,32,64,128
85+ // dst_offset is of size 32 bits
86+ // CONSTANT has to be decomposed into 32-bit chunks
87+ if (opcode == OpCode::SET) {
88+ switch (in_tag) {
89+ case AvmMemoryTag::U8:
90+ num_of_operands = 2 ;
91+ operands_size = 5 ;
92+ break ;
93+ case AvmMemoryTag::U16:
94+ num_of_operands = 2 ;
95+ operands_size = 6 ;
96+ break ;
97+ case AvmMemoryTag::U32:
98+ num_of_operands = 2 ;
99+ operands_size = 8 ;
100+ break ;
101+ case AvmMemoryTag::U64:
102+ num_of_operands = 3 ;
103+ operands_size = 12 ;
104+ break ;
105+ case AvmMemoryTag::U128:
106+ num_of_operands = 5 ;
107+ operands_size = 20 ;
108+ break ;
109+ default :
110+ throw std::runtime_error (" Instruction tag for SET opcode is invalid at position " +
111+ std::to_string (pos) + " value: " + std::to_string (in_tag_u8));
112+ break ;
113+ }
114+ } else {
115+ num_of_operands = Bytecode::OPERANDS_NUM.at (opcode);
116+ operands_size = AVM_OPERAND_BYTE_LENGTH * num_of_operands;
117+ }
118+
119+ if (pos + operands_size > length) {
120+ throw std::runtime_error (" Operand is missing at position " + std::to_string (pos));
121+ }
122+
123+ // We handle operands which are encoded with less than 4 bytes.
124+ // This occurs for opcode SET and tag U8 and U16.
125+ if (opcode == OpCode::SET && in_tag == AvmMemoryTag::U8) {
126+ operands.push_back (static_cast <uint32_t >(bytecode.at (pos)));
127+ pos++;
128+ num_of_operands--;
129+ } else if (opcode == OpCode::SET && in_tag == AvmMemoryTag::U16) {
130+ uint8_t const * ptr = &bytecode.at (pos);
131+ uint16_t operand{};
132+ serialize::read (ptr, operand);
133+ operands.push_back (static_cast <uint32_t >(operand));
134+ pos += 2 ;
135+ num_of_operands--;
136+ }
137+
138+ // Operands of size of 32 bits.
139+ for (size_t i = 0 ; i < num_of_operands; i++) {
140+ uint8_t const * ptr = &bytecode.at (pos);
141+ uint32_t operand{};
142+ serialize::read (ptr, operand);
143+ operands.push_back (operand);
144+ pos += AVM_OPERAND_BYTE_LENGTH;
145+ }
146+
147+ instructions.emplace_back (opcode, operands, static_cast <AvmMemoryTag>(in_tag));
148+ }
149+
150+ return instructions;
151+ }
152+
153+ /* *
154+ * @brief Generate the execution trace pertaining to the supplied instructions.
155+ *
156+ * @param instructions A vector of the instructions to be executed.
157+ * @param calldata expressed as a vector of finite field elements.
158+ * @return The trace as a vector of Row.
159+ */
160+ std::vector<Row> Execution::gen_trace (std::vector<Instruction> const & instructions, std::vector<FF> const & calldata)
161+ {
162+ AvmMiniTraceBuilder trace_builder{};
163+
164+ for (auto const & inst : instructions) {
165+ switch (inst.op_code ) {
166+ case OpCode::ADD:
167+ trace_builder.add (inst.operands .at (0 ), inst.operands .at (1 ), inst.operands .at (2 ), inst.in_tag );
168+ break ;
169+ case OpCode::SUB:
170+ trace_builder.sub (inst.operands .at (0 ), inst.operands .at (1 ), inst.operands .at (2 ), inst.in_tag );
171+ break ;
172+ case OpCode::MUL:
173+ trace_builder.mul (inst.operands .at (0 ), inst.operands .at (1 ), inst.operands .at (2 ), inst.in_tag );
174+ break ;
175+ case OpCode::DIV:
176+ trace_builder.div (inst.operands .at (0 ), inst.operands .at (1 ), inst.operands .at (2 ), inst.in_tag );
177+ break ;
178+ case OpCode::CALLDATACOPY:
179+ trace_builder.calldata_copy (inst.operands .at (0 ), inst.operands .at (1 ), inst.operands .at (2 ), calldata);
180+ break ;
181+ case OpCode::JUMP:
182+ trace_builder.jump (inst.operands .at (0 ));
183+ break ;
184+ case OpCode::INTERNALCALL:
185+ trace_builder.internal_call (inst.operands .at (0 ));
186+ break ;
187+ case OpCode::INTERNALRETURN:
188+ trace_builder.internal_return ();
189+ break ;
190+ case OpCode::SET: {
191+ uint32_t dst_offset{};
192+ uint128_t val{};
193+ switch (inst.in_tag ) {
194+ case AvmMemoryTag::U8:
195+ case AvmMemoryTag::U16:
196+ case AvmMemoryTag::U32:
197+ // U8, U16, U32 value represented in a single uint32_t operand
198+ val = inst.operands .at (0 );
199+ dst_offset = inst.operands .at (1 );
200+ break ;
201+ case AvmMemoryTag::U64: // value represented as 2 uint32_t operands
202+ val = inst.operands .at (0 );
203+ val <<= 32 ;
204+ val += inst.operands .at (1 );
205+ dst_offset = inst.operands .at (2 );
206+ break ;
207+ case AvmMemoryTag::U128: // value represented as 4 uint32_t operands
208+ for (size_t i = 0 ; i < 4 ; i++) {
209+ val += inst.operands .at (i);
210+ val <<= 32 ;
211+ }
212+ dst_offset = inst.operands .at (4 );
213+ break ;
214+ default :
215+ break ;
216+ }
217+ trace_builder.set (val, dst_offset, inst.in_tag );
218+ break ;
219+ }
220+ case OpCode::RETURN:
221+ trace_builder.return_op (inst.operands .at (0 ), inst.operands .at (1 ));
222+ break ;
223+ default :
224+ break ;
225+ }
226+ }
227+ return trace_builder.finalize ();
228+ }
229+
230+ } // namespace avm_trace
0 commit comments