Skip to content

Commit cf1c600

Browse files
committed
bytecode circuit with metadata
1 parent e7d62e6 commit cf1c600

File tree

4 files changed

+121
-79
lines changed

4 files changed

+121
-79
lines changed

crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/bytecode.hpp

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ namespace nil {
9898
std::vector<TYPE> push_size = std::vector<TYPE>(max_bytecode_size);
9999
std::vector<TYPE> length_left = std::vector<TYPE>(max_bytecode_size);
100100
std::vector<TYPE> value_rlc = std::vector<TYPE>(max_bytecode_size);
101+
std::vector<TYPE> is_byte = std::vector<TYPE>(max_bytecode_size);
101102

102103
if constexpr (stage == GenerationStage::ASSIGNMENT) {
103104
std::size_t cur = 0;
@@ -106,26 +107,27 @@ namespace nil {
106107
TYPE push_size_value = 0;
107108
auto buffer = bytecodes[i].first;
108109
TYPE length_left_value = buffer.size();
110+
// HEADER
111+
rlc_challenge[cur] = input.rlc_challenge;
112+
push_size[cur] = 0;
113+
length_left[cur] = length_left_value;
114+
value_rlc[cur] = length_left_value;
115+
is_byte[cur] = tag[cur] * (3 - tag[cur]) * TYPE(2).inversed();
116+
push_size_value = 0;
117+
length_left_value--;
118+
cur++;
119+
// BYTES
109120
for(std::size_t j = 0; j < bytecodes[i].first.size(); j++, cur++){
110121
auto byte = buffer[j];
111-
rlc_challenge[cur] = input.rlc_challenge;
112-
if( j == 0){ // HEADER
113-
push_size[cur] = 0;
114-
length_left[cur] = length_left_value;
115-
value_rlc[cur] = length_left_value;
116-
push_size_value = 0;
117-
length_left_value--;
118-
cur++;
119-
}
120-
// BYTE
121-
rlc_challenge[cur] = input.rlc_challenge;
122-
length_left[cur] = length_left_value;
123122
if(push_size_value == 0){
124123
if(byte > 0x5f && byte < 0x80) push_size_value = byte - 0x5f;
125124
} else {
126125
push_size_value--;
127126
}
127+
is_byte[cur] = tag[cur] * (3 - tag[cur]) * TYPE(2).inversed();
128128
push_size[cur] = push_size_value;
129+
rlc_challenge[cur] = input.rlc_challenge;
130+
length_left[cur] = length_left_value;
129131
value_rlc[cur] = value_rlc[cur - 1] * input.rlc_challenge + byte;
130132
length_left_value--;
131133
}
@@ -137,27 +139,28 @@ namespace nil {
137139
allocate(value_rlc[i],7,i);
138140
allocate(length_left[i],8,i);
139141
allocate(rlc_challenge[i],9,i);
142+
allocate(is_byte[i],10,i);
140143
}
141144
// constrain all bytecode values
142145
// if (make_links) {
143146
// copy_constrain(input.rlc_challenge, rlc_challenge[0]);
144147
// }
145148
static const auto zerohash = zkevm_keccak_hash({});
146149
for(std::size_t i = 0; i < max_bytecode_size; i++) {
147-
constrain(tag[i] * (tag[i] - 1)); // 0. TAG is zeroes or ones -- maybe there will be third value for non-used rows
148-
constrain((tag[i] - 1) * index[i]); // 1. INDEX for HEADER and unused bytes is zero
149-
constrain((tag[i] - 1) * (length_left[i] - value[i])); // 4. In contract header length_left == contract length
150+
constrain(tag[i] * (tag[i] - 1) * (tag[i] - 2)); // 0. TAG is zeroes, one or two
151+
constrain((tag[i] - 2) * (tag[i] - 1) * index[i]); // 1. INDEX for HEADER and unused bytes is zero
152+
constrain((tag[i] - 2) * (tag[i] - 1) * (length_left[i] - value[i])); // 4. In contract header length_left == contract length
150153
constrain(is_opcode[i] * (is_opcode[i] - 1)); // 7. is_opcode is zeroes or ones
151-
constrain((tag[i] - 1) * is_opcode[i]); // 8. is_opcode on HEADER are zeroes
152-
constrain((tag[i] - 1) * (value_rlc[i] - length_left[i])); // 14. value_rlc for HEADERS == 0;
154+
constrain((tag[i] - 2) * (tag[i] - 1) * is_opcode[i]); // 8. is_opcode on HEADER are zeroes
155+
constrain((tag[i] - 2) * (tag[i] - 1) * (value_rlc[i] - length_left[i])); // 14. value_rlc for HEADERS == 0;
153156

154157
if (i > 0) {
155-
constrain((tag[i-1] - 1) * index[i]); // 2. INDEX for first contract byte is zero
158+
constrain((tag[i-1] - 2) * (tag[i-1] - 1) * index[i]); // 2. INDEX for first contract byte is zero
156159
constrain(tag[i-1] * tag[i] * (index[i] - index[i-1] - 1)); // 3. INDEX is incremented for all bytes
157160
constrain(tag[i] * (length_left[i-1] - length_left[i] - 1)); // 5. In contract bytes each row decrement length_left
158-
constrain(tag[i-1] * (tag[i] - 1) * length_left[i-1]); // 6. Length_left is zero for last byte in the contract
159-
constrain((tag[i-1] - 1) * tag[i] * (is_opcode[i] - 1)); // 9. Fist is_opcode on BYTE after HEADER is 1
160-
constrain(tag[i] * (is_opcode[i] - 1) * (push_size[i-1] - push_size[i] - 1)); // 10. PUSH_SIZE decreases for non-opcodes
161+
constrain(tag[i-1] * (tag[i] - 2) * (tag[i] - 1) * length_left[i-1]); // 6. Length_left is zero for last byte in the contract
162+
constrain((tag[i] - 2) * (tag[i-1] - 1) * tag[i] * (is_opcode[i] - 1)); // 9. Fist is_opcode on BYTE after HEADER is 1
163+
constrain((tag[i] - 2) * tag[i] * (is_opcode[i] - 1) * (push_size[i-1] - push_size[i] - 1)); // 10. PUSH_SIZE decreases for non-opcodes except metadata
161164
constrain(is_opcode[i] * push_size[i-1]); // 11. before opcode push_size is always zero
162165
constrain(tag[i] * (hash_hi[i-1] - hash_hi[i])); //12. for all bytes hash is similar to previous
163166
constrain(tag[i] * (hash_lo[i-1] - hash_lo[i])); //13. for all bytes hash is similar to previous
@@ -167,15 +170,16 @@ namespace nil {
167170
if (i> 0 && i < max_bytecode_size-1) {
168171
constrain(tag[i+1] * (rlc_challenge[i] - rlc_challenge[i-1])); //17. rlc_challenge is similar for different contracts
169172
}
170-
lookup(tag[i]*value[i],"byte_range_table/full");
173+
lookup(tag[i]*value[i]*(2-tag[i]),"byte_range_table/full");
171174
lookup(std::vector<TYPE>({value[i]*is_opcode[i], push_size[i]*is_opcode[i], is_opcode[i]}),"zkevm_opcodes/full");
172175

173176
if( i > 0 ){
177+
//is last
174178
lookup(std::vector<TYPE>({
175179
tag[i] + 1 - tag[i], // TODO: update math::expression constructor with constant parameter
176-
tag[i-1] * (1 - tag[i]) * value_rlc[i-1],
177-
tag[i-1] * (1 - tag[i]) * hash_hi[i-1] + (1 - tag[i-1] * (1 - tag[i])) * w_hi<FieldType>(zerohash),
178-
tag[i-1] * (1 - tag[i]) * hash_lo[i-1] + (1 - tag[i-1] * (1 - tag[i])) * w_lo<FieldType>(zerohash)
180+
is_byte[i-1] * (1 - is_byte[i]) * value_rlc[i-1],
181+
is_byte[i-1] * (1 - is_byte[i]) * hash_hi[i-1] + (1 - is_byte[i-1] * (1 - is_byte[i])) * w_hi<FieldType>(zerohash),
182+
is_byte[i-1] * (1 - is_byte[i]) * hash_lo[i-1] + (1 - is_byte[i-1] * (1 - is_byte[i])) * w_lo<FieldType>(zerohash)
179183
}), "keccak_table");
180184
}
181185
}
Lines changed: 70 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//---------------------------------------------------------------------------//
22
// Copyright (c) 2024 Elena Tatuzova <[email protected]>
3+
// Copyright (c) 2025 Antoine Cyr <[email protected]>
34
//
45
// MIT License
56
//
@@ -30,8 +31,6 @@
3031
namespace nil {
3132
namespace blueprint {
3233
namespace bbf {
33-
// Component for bytecode table
34-
3534
template<typename FieldType, GenerationStage stage>
3635
class bytecode_table : public generic_component<FieldType, stage> {
3736
using typename generic_component<FieldType, stage>::context_type;
@@ -41,86 +40,104 @@ namespace nil {
4140
using generic_component<FieldType, stage>::lookup;
4241
using generic_component<FieldType, stage>::lookup_table;
4342

44-
public:
45-
using typename generic_component<FieldType,stage>::TYPE;
43+
public:
44+
using TYPE = typename generic_component<FieldType, stage>::TYPE;
4645
using input_type = std::conditional_t<
4746
stage == GenerationStage::ASSIGNMENT, zkevm_keccak_buffers, std::monostate
4847
>;
4948

5049
std::size_t max_bytecode_size;
5150

52-
// interfaces for interaction with other components:
53-
std::vector<TYPE> tag = std::vector<TYPE>(max_bytecode_size);
54-
std::vector<TYPE> index = std::vector<TYPE>(max_bytecode_size);
55-
std::vector<TYPE> value = std::vector<TYPE>(max_bytecode_size);
56-
std::vector<TYPE> is_opcode = std::vector<TYPE>(max_bytecode_size);
57-
std::vector<TYPE> hash_hi = std::vector<TYPE>(max_bytecode_size);
58-
std::vector<TYPE> hash_lo = std::vector<TYPE>(max_bytecode_size);
51+
std::vector<TYPE> tag = std::vector<TYPE>(max_bytecode_size); // 0: header, 1: executable bytes, 2: metadata
52+
std::vector<TYPE> index = std::vector<TYPE>(max_bytecode_size); // Position in bytecode
53+
std::vector<TYPE> value = std::vector<TYPE>(max_bytecode_size); // Byte value or total length
54+
std::vector<TYPE> is_opcode = std::vector<TYPE>(max_bytecode_size); // 1 if opcode, 0 otherwise
55+
std::vector<TYPE> hash_hi = std::vector<TYPE>(max_bytecode_size); // High 128 bits of hash
56+
std::vector<TYPE> hash_lo = std::vector<TYPE>(max_bytecode_size); // Low 128 bits of hash
5957

60-
static std::size_t get_witness_amount(){
61-
return 6;
62-
}
58+
static std::size_t get_witness_amount() { return 6; }
6359

6460
bytecode_table(context_type &context_object,
65-
const input_type &input,
66-
std::size_t max_bytecode_size_,
67-
bool make_links = true) :
68-
max_bytecode_size(max_bytecode_size_),
69-
generic_component<FieldType,stage>(context_object) {
61+
const input_type &input,
62+
std::size_t max_bytecode_size_,
63+
bool make_links = true)
64+
: max_bytecode_size(max_bytecode_size_),
65+
generic_component<FieldType, stage>(context_object) {
7066

71-
// if we're in assignment stage, prepare all the values
7267
if constexpr (stage == GenerationStage::ASSIGNMENT) {
7368
auto bytecodes = input.get_data();
74-
7569
std::size_t cur = 0;
76-
for(std::size_t i = 0; i < bytecodes.size(); i++) {
70+
71+
for (std::size_t i = 0; i < bytecodes.size(); i++) {
7772
TYPE hash_hi_val = w_hi<FieldType>(bytecodes[i].second);
7873
TYPE hash_lo_val = w_lo<FieldType>(bytecodes[i].second);
7974
TYPE push_size = 0;
8075
const auto &buffer = bytecodes[i].first;
81-
for(std::size_t j = 0; j < buffer.size(); j++, cur++){
76+
std::size_t total_len = buffer.size();
77+
78+
// Compute metadata length from the last two bytes
79+
std::size_t exec_boundary = total_len; // Default: all bytes executable
80+
if (total_len >= 2) {
81+
std::size_t meta_len = (buffer[total_len - 2] << 8) + buffer[total_len - 1];
82+
if (meta_len + 2 <= total_len) {
83+
std::size_t boundary = total_len - meta_len - 2 - 1; // Byte before metadata
84+
if (boundary < total_len && (buffer[boundary] == 0x00 || buffer[boundary] == 0xfe || buffer[boundary] == 0xf3)) {
85+
exec_boundary = boundary + 1; // After stopping opcode
86+
}
87+
}
88+
}
89+
90+
// Header
91+
BOOST_ASSERT(cur < max_bytecode_size);
92+
tag[cur] = 0;
93+
index[cur] = 0;
94+
value[cur] = total_len;
95+
is_opcode[cur] = 0;
96+
hash_hi[cur] = hash_hi_val;
97+
hash_lo[cur] = hash_lo_val;
98+
cur++;
99+
100+
// Bytes
101+
for (std::size_t j = 0; j < buffer.size(); j++, cur++) {
82102
BOOST_ASSERT(cur < max_bytecode_size);
83103
std::uint8_t byte = buffer[j];
84-
hash_hi[cur] = hash_hi_val;
85-
hash_lo[cur] = hash_lo_val;
86-
if( j == 0) { // HEADER
87-
value[cur] = buffer.size();
88-
tag[cur] = 0;
89-
index[cur] = 0;
90-
is_opcode[cur] = 0;
91-
push_size = 0; // might be unnecessary
92-
cur++;
93-
}
94-
// BYTE
95104
value[cur] = byte;
96105
hash_hi[cur] = hash_hi_val;
97106
hash_lo[cur] = hash_lo_val;
98-
tag[cur] = 1;
99107
index[cur] = j;
100-
if (push_size == 0) {
101-
is_opcode[cur] = 1;
102-
if (byte > 0x5f && byte < 0x80) push_size = byte - 0x5f;
103-
} else {
108+
109+
if (j < exec_boundary) { // Executable bytes
110+
tag[cur] = 1;
111+
if (push_size == 0) {
112+
is_opcode[cur] = 1;
113+
if (byte > 0x5f && byte < 0x80) {
114+
push_size = byte - 0x5f;
115+
}
116+
} else {
117+
is_opcode[cur] = 0;
118+
push_size--;
119+
}
120+
} else { // Metadata bytes
121+
tag[cur] = 2;
104122
is_opcode[cur] = 0;
105-
push_size--;
123+
push_size = 0; // Reset to avoid carry-over
106124
}
107125
//std::cout << cur << ". " << std::hex << std::size_t(byte) << " " << is_opcode[cur] << " " << push_size << std::dec << std::endl;
108126
}
109127
}
110128
}
111-
// allocate everything. NB: this replaces the map from the original component
112-
for(std::size_t i = 0; i < max_bytecode_size; i++) {
113-
allocate(tag[i],0,i);
114-
allocate(index[i],1,i);
115-
allocate(value[i],2,i);
116-
allocate(is_opcode[i],3,i);
117-
allocate(hash_hi[i],4,i);
118-
allocate(hash_lo[i],5,i);
129+
130+
for (std::size_t i = 0; i < max_bytecode_size; i++) {
131+
allocate(tag[i], 0, i);
132+
allocate(index[i], 1, i);
133+
allocate(value[i], 2, i);
134+
allocate(is_opcode[i], 3, i);
135+
allocate(hash_hi[i], 4, i);
136+
allocate(hash_lo[i], 5, i);
119137
}
120-
// declare dynamic lookup table
121-
lookup_table("zkevm_bytecode",std::vector<std::size_t>({0,1,2,3,4,5}),0,max_bytecode_size);
122-
};
123-
};
138+
lookup_table("zkevm_bytecode", std::vector<std::size_t>({0, 1, 2, 3, 4, 5}), 0, max_bytecode_size);
139+
}
140+
};
124141
}
125142
}
126-
}
143+
}

0 commit comments

Comments
 (0)