总体布局
本段介绍结构体PublicCircuitConfig
的列及其含义。
单功能列 Single-purpose columns
下面是public子电路中的单功能的列:
/// various public information tag, e.g. BlockNumber, TxFrom
tag: Column<Instance>,
/// tx_id (start from 1), except for tag=BlockHash, means recent block number diff (1...256)
tx_idx_or_number_diff: Column<Instance>,
pub enum Tag {
#[default]
ChainId
BlockCoinbase,
BlockTimestamp,
BlockNumber,
BlockDifficulty,
BlockGasLimit,
BlockBaseFee,
BlockHash,
TxStatus,
// combine From and Value together to reduce number of lookups
TxFromValue,
// combine To and CallDataLength together to reduce number of lookups
TxToCallDataSize,
TxIsCreate,
TxGasLimit,
TxGasPrice,
TxCalldata, //TODO make sure this equals copy tag PublicCalldata
TxLog,
TxLogSize,
CodeSize,
}
- tx_idx_or_number_diff 指交易id;当该行数据为BlockHash的时候指的是最近的block number
多功能列 Versatile columns
为了减少列的使用,缩减电路规模,设计了多功能的列。在不同的行数据类别下,这些列存放不同的数据。目前设计有4个多功能列,代码里呈现为:
values: [Column<Instance>; NUM_VALUES],
表设计
区块公共数据存放
tag为ChainId时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
ChainId |
0 |
chain_id[..16] |
chain_id[16..] |
0 |
0 |
tag为BlockCoinbase时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockCoinbase |
0 |
coinbase[..4] |
coinbase[4..] |
0 |
0 |
tag为BlockTimeStamp时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockTimestamp |
0 |
BlockTimestamp[..16] |
BlockTimestamp[16..] |
0 |
0 |
tag为BlockNumber时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockNumber |
0 |
0 |
BlockNumber |
0 |
0 |
tag为BlockDifficulty时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockDifficulty |
0 |
BlockDifficulty[..16] |
BlockDifficulty[16..] |
0 |
0 |
tag为BlockGasLimit时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockGasLimit |
0 |
BlockGasLimit[..16] |
BlockGasLimit[16..] |
0 |
0 |
tag为BlockBaseFee时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockBaseFee |
0 |
BlockBaseFee[..16] |
BlockBaseFee[16..] |
0 |
0 |
tag为BlockHash的时候,遍历最近的history_hash,每行存放数据值如下,其中index代表在history_hash中的次序(也是当前区块number-此区块number的差值),hash为该次序对应的hash值:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
BlockHash |
index |
hash[..16] |
hash[16..] |
0 |
0 |
区块交易相关数据存放
对区块中的每一笔交易遍历存放如下数据类型,其中tx_idx为该交易在区块中的交易列表中的序号(从1开始)
tag为TxFromValue时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxFromValue |
tx_idx |
from[..4] |
from[4..] |
value[..16] |
value[16..] |
tag为TxToCallDataSize时,该行数据值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxToCallDataSize |
tx_idx |
to_hi |
to_lo |
0 |
tx.input.len |
tag为TxIsCreate时,该行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxIsCreate |
tx_idx |
0 |
1/0(创建合约为1,普通合约调用为0) |
0 |
0 |
tag为TxGasLimit时,该行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxGasLimit |
tx_idx |
gas[..16] |
gas[16..] |
0 |
0 |
tag为TxGasPrice时,该行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxGasPrice |
tx_idx |
gas_price[..16] |
gas_price[16..] |
0 |
0 |
将合约的输入数据按byte遍历, 每个byte存放进TxCalldata的数据行,其中idx为该byte在合约的输入数据中的次序,byte为该次序对应的byte数据,该行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxCalldata |
tx_idx |
idx |
byte |
0 |
0 |
区块log数据存放
区块中每笔交易可以产生多条log,遍历区块中每笔交易的每个log,其中topic_num为该log中topics的数目(不超过4);tx_idx为该log所属的transaction_index;log_index为该log的log_index;address为该log所属的地址;如果topic_num为0的话,log_tag为AddrWith0Topic;如果topic_num为1/2/3/4的话,log_tag为AddrWith1/2/3/4Topic。
tag为TxLog,该行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxLog |
tx_idx |
log_index |
log_tag |
address[..4] |
address[4..] |
tag为TxLog的时候,根据topic的数目,依次存放topic1/2/3/4的数据,其中topic_hash为该topic对应的hash,topic_log_tag为Topic1/2/3/4,每行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxLog |
tx_idx |
log_index |
topic_log_tag |
topic_hash[..16] |
topic_hash[16..] |
tag为TxLog时候,记录了log的data长度(data_len),该行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxLog |
tx_idx |
log_index |
log_tag=DataSize |
0 |
data_len |
将log中的数据按照byte便利,每个byte存放一行,其中data_idx为该byte在数据块中的次序,其中LogTag::Data为该数据类型,每行数值为:
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
TxLog |
tx_idx |
log_index |
LogTag::Data |
byte |
data_idx |
合约公共数据存放
tag为CodeSize时,该行数据值为:
code_size hi的值应该为0,因为EVM的硬性要求(code size均不超过约4万)
tag |
tx_idx_or_number_diff |
value[0] |
values[1] |
values[2] |
values[3] |
CodeSize |
0 |
code_addr hi |
code_addr lo |
code_size hi |
code_size lo |
2024年5月更新
上述所有列都变为 Column<Advice>
。
问题1
如何输入公开数据到zk电路里?
解决方法:将上述所有列所有行进行哈希。将哈希值作为 Column<Instance>
,仅需1列。2行,存hash_hi,lo
。
问题2
上述有6列,怎么做哈希?
解决方法:假设6列的值都是u8,那将6列按列连接起来成为一个u8的数组进行哈希。我们的KeccakTable是用输入的RLC进行lookup的。注意到,连接起来的数组的CONCAT_RLC = R^5 RLC[0] + R^4 RLC[1] + R^3 RLC[2] + R^2 RLC[3] + R^1 RLC[4] + RLC[5]。其中RLC[]是六列分别的RLC。R是用于RLC的随机数r的length次方。length是整个Public table的有效数据在u8表示下的长度。
因此步骤是:
- 将原来table的数值转化成u8(见下段)
- 建一列cnt,从0开始数。建一列length,所有值都是最终的length。
- 求得6列的RLC(需要新一列记录rlc累加值)
- 求得随机数r的length次方(需要新一列记录次方的累计值)
- 求CONCAT_RLC
- 使用length,CONCAT_RLC向KeccakTable进行lookup,得到hash的hi,lo(在新建的一个或两个
Column<Advice>
中,新建的两个列,可以每行都是hash的hi,lo). lookup条件cnt==length-1
- 使用equal约束,
Column<Advice>
中的hash和Column<Instance>
中的hash相等。
问题3
怎么将原来table的数值转化成u8?
解决方法:上述的value都是在U128范围内的,因此,原来table的一个数值可以变为16个u8数值。那么原来table的一列就要新建一列,原来的一列中的一行的value,在新列中要占用16行。
再新建一列ring_16,以15,14...1,0这样循环。在ring_16==0时,对新列中16行和原来列的1行的value进行约束,证明16个u8拼成了一个value。
这样,原来table的两行之间,要插入15行的填充行,填充行没有任何意义。lookup也要加上ring_16,且lookup时ring_16=0。
我们原来table有6列,因此需要进行6次这样的操作。