1. 引言
NEAR官方在2020年10月发布了一则 Succinct Proof for NEAR light client的悬赏。
NEAR采用的是ed25519签名。
NEAR light client应:
- 压缩the light block verification 到 仅需验证一个单一的proof,这样可快速同步区块并用于Rainbow bridge中。
NEAR light client希望达成的目标有:
- 1)Light client proof generator, that fetches specific block by height from the node over RPC and generates a succinct proof that takes block hash and a block from previous epoch as arguments。
- 2)提供多种blocks proofs的测试用例,包括但不限于:epoch的最后一个区块、epoch的第一个区块、epoch的中间区块、无效区块(如无效data,无效签名等)、扫描NEAR主网所有区块生成proof并verify。
- 3)Solidity携程的Proof verifier合约,输入为某previous epoch的某block hash和block,输出为a proof。
- 4)从创世区块到指定区块的recursively proof。
2. Light client
NEAR light client state定义为:
- 当前区块头
BlockHeaderInnerLiteView
:包含了height
、epoch_id
、next_epoch_id
、prev_state_root
、outcome_root
、timestamp
。 - 下一epoch的block producers set的hash值
next_bp_hash
。 - 所有区块hashes的merkle root
block_merkle_root
。 - 当前epoch的block producers set。
- 下一epoch的block producers set。
Light client需定期通过特殊的RPC end-point来获取LightClientBlockView
instances。
Light client并不需要收所有区块的LightClientBlockView
。具有区块
B
B
B的LightClientBlockView
,就足以验证区块B及其之前任意区块的state或outcome statement。
但是,为了验证某特定LightClientBlockView
的有效性,light client必须验证a LightClientBlockView
for at least one block in the preceding epoch,从而,在每个epoch需向light client至少同步并验证一个LightClientBlockView
。
2.1 Validating light client block views
相关结构体定义为:
pub enum ApprovalInner {
Endorsement(CryptoHash),
Skip(BlockHeight)
}
pub struct ValidatorStakeView {
pub account_id: AccountId,
pub public_key: PublicKey,
pub stake: Balance,
}
pub struct BlockHeaderInnerLiteView {
pub height: BlockHeight,
pub epoch_id: CryptoHash,
pub next_epoch_id: CryptoHash,
pub prev_state_root: CryptoHash,
pub outcome_root: CryptoHash,
pub timestamp: u64,
pub next_bp_hash: CryptoHash,
pub block_merkle_root: CryptoHash,
}
pub struct LightClientBlockLiteView {
pub prev_block_hash: CryptoHash,
pub inner_rest_hash: CryptoHash,
pub inner_lite: BlockHeaderInnerLiteView,
}
pub struct LightClientBlockView {
pub prev_block_hash: CryptoHash,
pub next_block_inner_hash: CryptoHash,
pub inner_lite: BlockHeaderInnerLiteView,
pub inner_rest_hash: CryptoHash,
pub next_bps: Option<Vec<ValidatorStakeView>>,
pub approvals_after_next: Vec<Option<Signature>>,
}
block hash的计算方式为:
sha256(concat(
sha256(concat(
sha256(borsh(inner_lite)),
sha256(borsh(inner_rest))
)),
prev_hash
))
可根据prev_block_hash
、next_block_inner_hash
和 inner_rest_hash
来重构当前区块hash和下一区块hash。approval_message为待签名消息,其中block_view
为an instance of LightClientBlockView
:
def reconstruct_light_client_block_view_fields(block_view):
current_block_hash = sha256(concat(
sha256(concat(
sha256(borsh(block_view.inner_lite)),
block_view.inner_rest_hash,
)),
block_view.prev_block_hash
))
next_block_hash = sha256(concat(
block_view.next_block_inner_hash,
current_block_hash
))
approval_message = concat(
borsh(ApprovalInner::Endorsement(next_block_hash)),
little_endian(block_view.inner_lite.height + 2)
)
return (current_block_hash, next_block_hash, approval_message)
当且仅当满足以下条件时,light client才会根据LightClientBlockView
来更新区块头:
- 1)区块高度 高于 当前高度;
- 2)区块中的epoch 等于当前head中的
epoch_id
或next_epoch_id
; - 3)若区块中的epoch 等于当前head中的
next_epoch_id
,则next_bps
不为None
; - 4)
approvals_after_next
中包含了来自于相应epoch的block producers对approval_message
的有效签名; - 5)
approvals_after_next
中的签名对应more than 2/3 of the total stake; - 6)若
next_bps
不为none,则sha256(borsh(next_bps))
对应为inner_lite
中的next_bp_hash
。
def validate_and_update_head(block_view):
global head
global epoch_block_producers_map
current_block_hash, next_block_hash, approval_message = reconstruct_light_client_block_view_fields(block_view)
# (1)
if block_view.inner_lite.height <= head.inner_lite.height:
return False
# (2)
if block_view.inner_lite.epoch_id not in [head.inner_lite.epoch_id, head.inner_lite.next_epoch_id]:
return False
# (3)
if block_view.inner_lite.epoch_id == head.inner_lite.next_epoch_id and block_view.next_bps is None:
return False
# (4) and (5)
total_stake = 0
approved_stake = 0
epoch_block_producers = epoch_block_producers_map[block_view.inner_lite.epoch_id]
for maybe_signature, block_producer in zip(block_view.approvals_after_next, epoch_block_producers):
total_stake += block_producer.stake
if maybe_signature is None:
continue
approved_stake += block_producer.stake
if not verify_signature(
public_key: block_producer.public_key,
signature: maybe_signature,
message: approval_message
):
return False
threshold = total_stake * 2 // 3
if approved_stake <= threshold:
return False
# (6)
if block_view.next_bps is not None:
if sha256(borsh(block_view.next_bps)) != block_view.inner_lite.next_bp_hash:
return False
epoch_block_producers_map[block_view.inner_lite.next_epoch_id] = block_view.next_bps
head = block_view
2.2 验签
为了简化,此时要求LightClientBlockView
对应的区块、下一区块、下下个区块 均属于同一epoch。从而保证每个epoch至少有一个final block for which the next two blocks that build on top of it are in the same epoch。
当LightClientBlockView
验证通过时,该epoch的block producer set即已知。当前一epoch的第一个light client block view处理完毕时,根据以上第(3)条可知next_bps
不为None
,根据以上第(6)条可知其对应block header中的next_bp_hash
,根据以上第(5)条可知,前一epoch的next_bps
的所有质押之和为total_stake
。
LightClientBlockView::approvals_after_next
中的签名为对approval_message
的签名。approvals_after_next
中第
i
i
i个签名若存在,则必须可由前一epoch的next_bps
中的第
i
i
i个公钥验签通过。approvals_after_next
中的元素数可少于前一epoch的next_bps
中的元素数。
approvals_after_next
中的元素数也可多于前一epoch的next_bps
中的元素数。因为根据 NEAR共识机制 可知,每个epoch的最后一个区块包含了来自当前epoch以及下一epoch的block producers的签名。多余的签名light client可安全地直接忽略。
2.3 Proof Verification
2.3.1 Transaction Outcome Proofs
为了验证某交易或receipt在链上已发生,light client必须通过RPC接口通过提供id
和light client head的block hash 来获取proof,id
类型为:
pub enum TransactionOrReceiptId {
Transaction { hash: CryptoHash, sender: AccountId },
Receipt { id: CryptoHash, receiver: AccountId },
}
RPC接口会返回如下结构体:
pub struct RpcLightClientExecutionProofResponse {
/// Proof of execution outcome
pub outcome_proof: ExecutionOutcomeWithIdView,
/// Proof of shard execution outcome root
pub outcome_root_proof: MerklePath,
/// A light weight representation of block that contains the outcome root
pub block_header_lite: LightClientBlockLiteView,
/// Proof of the existence of the block in the block merkle tree,
/// which consists of blocks up to the light client head
pub block_proof: MerklePath,
}
包含了light client证明the execution outcome of the given transaction or receipt的所有信息。
其中ExecutionOutcomeWithIdView
定义为:
pub struct ExecutionOutcomeWithIdView {
/// Proof of the execution outcome
pub proof: MerklePath,
/// Block hash of the block that contains the outcome root
pub block_hash: CryptoHash,
/// Id of the execution (transaction or receipt)
pub id: CryptoHash,
/// The actual outcome
pub outcome: ExecutionOutcomeView,
}
proof verification可分为2步:
- 1)execution outcome root verification
- 2)block merkle root verification
(1)execution outcome root verification:
若区块
H
H
H中包含了the outcome root of the transaction or receipt,则outcome_proof
中更包含了
H
H
H的区块hash,以及其所属shard的the merkle proof of the execution outcome。
H
H
H的outcome root可按如下方式重构:
shard_outcome_root = compute_root(sha256(borsh(execution_outcome)), outcome_proof.proof)
block_outcome_root = compute_root(sha256(borsh(shard_outcome_root)), outcome_root_proof)
该outcome root必须匹配block_header_lite.inner_lite
中的outcome root。
(2)block merkle root verification
根据LightClientBlockLiteView
计算block hash的方法为:
sha256(concat(
sha256(concat(
sha256(borsh(inner_lite)),
sha256(borsh(inner_rest))
)),
prev_hash
))
期待的block merkle root可按如下方式计算:
block_hash = compute_block_hash(block_header_lite)
block_merkle_root = compute_root(block_hash, block_proof)
其必须匹配the block merkle root in the light client block of the light client head。
2.4 RPC接口
2.4.1 Light client block
NEAR的每个全节点为light client提供了接口来获取新的LightClientBlockView
:
http post http://127.0.0.1:3030/ jsonrpc=2.0 method=next_light_client_block params:="[<last known hash>]" id="dontcare"
该RPC接口会返回LightClientBlock
for the block as far into the future from the last known hash as possible for the light client to still accept it。特别地,其要么返回下一epoch的最后一个final block,要么返回the last final known block。当没有更新的final block than the one the light client knows about,该RPC接口会返回empty result。
A standalone light client would bootstrap by requesting next blocks until it receives an empty result, and then periodically request the next light client block.
A smart contract-based light client that enables a bridge to NEAR on a different blockchain naturally cannot request blocks itself. Instead external oracles query the next light client block from one of the full nodes, and submit it to the light client smart contract. The smart contract-based light client performs the same checks described above, so the oracle doesn’t need to be trusted.
2.4.2 Light client proof
以下RPC接口可返回light client验证execution outcomes所需的RpcLightClientExecutionProofResponse
。
对于transaction execution outcome,相应的RPC接口为:
http post http://127.0.0.1:3030/ jsonrpc=2.0 method=EXPERIMENTAL_light_client_proof params:="{"type": "transaction", "transaction_hash": <transaction_hash>, "sender_id": <sender_id>, "light_client_head": <light_client_head>}" id="dontcare"
对于receipt execution outcome,相应的RPC接口为:
http post http://127.0.0.1:3030/ jsonrpc=2.0 method=EXPERIMENTAL_light_client_proof params:="{"type": "receipt", "receipt_id": <receipt_id>, "receiver_id": <receiver_id>, "light_client_head": <light_client_head>}" id="dontcare"
参考资料
[1] https://nomicon.io/ChainSpec/LightClient.html
[2] Rainbow Upgrade Idea: Using zk-SNARKS to cheaply verify ED25519 signatures on Ethereum mainnet