Skip to content

Commit 28c4793

Browse files
authored
feat(avm): Bytecode parsing and proof generation (AztecProtocol#4191)
Resolves AztecProtocol#3791
1 parent 1886310 commit 28c4793

File tree

17 files changed

+936
-55
lines changed

17 files changed

+936
-55
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include "./utils.hpp"
2+
3+
namespace bb::utils {
4+
5+
std::vector<uint8_t> hex_to_bytes(const std::string& hex)
6+
{
7+
std::vector<uint8_t> bytes;
8+
9+
for (unsigned int i = 0; i < hex.length(); i += 2) {
10+
std::string byteString = hex.substr(i, 2);
11+
bytes.push_back(static_cast<uint8_t>(strtol(byteString.c_str(), nullptr, 16)));
12+
}
13+
14+
return bytes;
15+
}
16+
17+
} // namespace bb::utils
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <string>
5+
#include <vector>
6+
7+
namespace bb::utils {
8+
9+
/**
10+
* @brief Routine to transform hexstring to vector of bytes.
11+
*
12+
* @param Hexadecimal string representation.
13+
* @return Vector of uint8_t values.
14+
*/
15+
std::vector<uint8_t> hex_to_bytes(const std::string& hex);
16+
17+
} // namespace bb::utils

barretenberg/cpp/src/barretenberg/crypto/ecdsa/ecdsa.test.cpp

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "ecdsa.hpp"
22
#include "barretenberg/common/serialize.hpp"
3+
#include "barretenberg/common/utils.hpp"
34
#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp"
45
#include "barretenberg/ecc/curves/secp256r1/secp256r1.hpp"
56
#include "barretenberg/serialize/test_helper.hpp"
@@ -89,23 +90,10 @@ TEST(ecdsa, recover_public_key_secp256r1_sha256)
8990
EXPECT_EQ(recovered_public_key, account.public_key);
9091
}
9192

92-
std::vector<uint8_t> HexToBytes(const std::string& hex)
93-
{
94-
std::vector<uint8_t> bytes;
95-
96-
for (unsigned int i = 0; i < hex.length(); i += 2) {
97-
std::string byteString = hex.substr(i, 2);
98-
uint8_t byte = (uint8_t)strtol(byteString.c_str(), NULL, 16);
99-
bytes.push_back(byte);
100-
}
101-
102-
return bytes;
103-
}
104-
10593
TEST(ecdsa, check_overflowing_r_and_s_are_rejected)
10694
{
10795

108-
std::vector<uint8_t> message_vec = HexToBytes("41414141");
96+
std::vector<uint8_t> message_vec = utils::hex_to_bytes("41414141");
10997

11098
std::string message(message_vec.begin(), message_vec.end());
11199
crypto::ecdsa_signature signature;
@@ -181,10 +169,10 @@ TEST(ecdsa, verify_signature_secp256r1_sha256_NIST_1)
181169
};
182170

183171
crypto::ecdsa_signature sig{ r, s, 27 };
184-
std::vector<uint8_t> message_vec =
185-
HexToBytes("5905238877c77421f73e43ee3da6f2d9e2ccad5fc942dcec0cbd25482935faaf416983fe165b1a045ee2bcd2e6dca3bdf46"
186-
"c4310a7461f9a37960ca672d3feb5473e253605fb1ddfd28065b53cb5858a8ad28175bf9bd386a5e471ea7a65c17cc934a9"
187-
"d791e91491eb3754d03799790fe2d308d16146d5c9b0d0debd97d79ce8");
172+
std::vector<uint8_t> message_vec = utils::hex_to_bytes(
173+
"5905238877c77421f73e43ee3da6f2d9e2ccad5fc942dcec0cbd25482935faaf416983fe165b1a045ee2bcd2e6dca3bdf46"
174+
"c4310a7461f9a37960ca672d3feb5473e253605fb1ddfd28065b53cb5858a8ad28175bf9bd386a5e471ea7a65c17cc934a9"
175+
"d791e91491eb3754d03799790fe2d308d16146d5c9b0d0debd97d79ce8");
188176
std::string message(message_vec.begin(), message_vec.end());
189177

190178
bool result = crypto::ecdsa_verify_signature<Sha256Hasher, secp256r1::fq, secp256r1::fr, secp256r1::g1>(

barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_common.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp"
44
#include "barretenberg/proof_system/circuit_builder/generated/AvmMini_circuit_builder.hpp"
5+
#include <cstdint>
56

67
using Flavor = bb::honk::flavor::AvmMiniFlavor;
78
using FF = Flavor::FF;
@@ -12,6 +13,9 @@ namespace avm_trace {
1213
// Number of rows
1314
static const size_t AVM_TRACE_SIZE = 256;
1415
enum class IntermRegister : uint32_t { IA = 0, IB = 1, IC = 2 };
16+
17+
// Keep following enum in sync with MAX_NEM_TAG below
1518
enum class AvmMemoryTag : uint32_t { U0 = 0, U8 = 1, U16 = 2, U32 = 3, U64 = 4, U128 = 5, FF = 6 };
19+
static const uint32_t MAX_MEM_TAG = 6;
1620

1721
} // namespace avm_trace
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include "barretenberg/plonk/proof_system/types/proof.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_trace.hpp"
7+
#include <cstddef>
8+
#include <cstdint>
9+
#include <vector>
10+
11+
namespace avm_trace {
12+
13+
class Execution {
14+
public:
15+
Execution() = default;
16+
17+
static size_t const AVM_OPERAND_BYTE_LENGTH = 4; // Keep in sync with TS code
18+
static_assert(sizeof(uint32_t) / sizeof(uint8_t) == AVM_OPERAND_BYTE_LENGTH);
19+
20+
static size_t const AVM_OPCODE_BYTE_LENGTH = 1; // Keep in sync with TS code
21+
static size_t const AVM_IN_TAG_BYTE_LENGTH = 1; // Keep in sync with TS code
22+
23+
static std::vector<Instruction> parse(std::vector<uint8_t> const& bytecode);
24+
static std::vector<Row> gen_trace(std::vector<Instruction> const& instructions, std::vector<FF> const& calldata);
25+
static plonk::proof run_and_prove(std::vector<uint8_t> const& bytecode, std::vector<FF> const& calldata);
26+
};
27+
28+
} // namespace avm_trace
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include "barretenberg/vm/avm_trace/AvmMini_common.hpp"
4+
#include "barretenberg/vm/avm_trace/AvmMini_opcode.hpp"
5+
#include <cstdint>
6+
#include <vector>
7+
8+
namespace avm_trace {
9+
10+
class Instruction {
11+
public:
12+
OpCode op_code;
13+
std::vector<uint32_t> operands;
14+
AvmMemoryTag in_tag;
15+
16+
Instruction() = delete;
17+
explicit Instruction(OpCode op_code, std::vector<uint32_t> operands, AvmMemoryTag in_tag)
18+
: op_code(op_code)
19+
, operands(std::move(operands))
20+
, in_tag(in_tag){};
21+
};
22+
23+
} // namespace avm_trace

0 commit comments

Comments
 (0)