Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): proof verification aggregation #17938

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents {
/// @inheritdoc ITaikoL1
function proveBlocks(
uint64[] calldata _blockIds,
bytes[] calldata _inputArr
bytes[] calldata _inputArr,
bytes proof
)
external
whenNotPaused
Expand All @@ -162,9 +163,14 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents {

TaikoData.Config memory config = getConfig();

IVerifier.Context[] memory ctxs = new IVerifier.Context[](_blockIds.length);
TaikoData.Transition[] memory trans = new TaikoData.Transition[](_blockIds.length);

for (uint256 i; i < _blockIds.length; ++i) {
_proveBlock(_blockIds[i], _inputArr[i], config);
(ctxs[i], trans[i]) = _proveBlock(_blockIds[i], _inputArr[i], config);
}

IVerifier(verifier).verifyProof(ctxs, trans, proof);
Copy link
Contributor

@adaki2004 adaki2004 Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a compilation error, due to this outter verifyProof(), the verifier variable is not know. It is coming from the tier configuration itself (now, in our current design).

And also this yields into another problem, with an example:
The block tiers (and hence the tier verifiers) can be for example:
block nr. 102: SGX only
block nr. 103: SGX+RISC0
block nr. 104: SGX+SP1
block nr. 105: SGX only again

And thus, the verifier contract address itself differs from block to block. so aggregating multiple, continuous blocks and verify with one go, will only work (in the current design at least!) if we have the exact same logic (contract) to verify (so same tiers), otherwise no.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can only aggregate proofs for the same tier together, but the proofs may correspond to blocks that are not consecutive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but then with ever 500-100th zk tier blocks in practice it will be useful for tee only. (At least for now.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have now added the required checks to make sure only the same proofs can be aggregrated.

}

/// @inheritdoc ITaikoL1
Expand Down
6 changes: 2 additions & 4 deletions packages/protocol/contracts/L1/libs/LibProving.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ library LibProving {
bytes calldata _input
)
public
returns (IVerifier.Context memory ctx, TaikoData.Transition memory tran)
{
Local memory local;

Expand All @@ -144,7 +145,6 @@ library LibProving {
local.postFork = _blockId >= _config.ontakeForkHeight;

TaikoData.BlockMetadataV2 memory meta;
TaikoData.Transition memory tran;
TaikoData.TierProof memory proof;

if (local.postFork) {
Expand Down Expand Up @@ -260,7 +260,7 @@ library LibProving {
address verifier = _resolver.resolve(local.tier.verifierName, false);
bool isContesting = proof.tier == ts.tier && local.tier.contestBond != 0;

IVerifier.Context memory ctx = IVerifier.Context({
ctx = IVerifier.Context({
metaHash: local.metaHash,
blobHash: meta.blobHash,
// Separate msgSender to allow the prover to be any address in the future.
Expand All @@ -270,8 +270,6 @@ library LibProving {
isContesting: isContesting,
blobUsed: meta.blobUsed
});

IVerifier(verifier).verifyProof(ctx, tran, proof);
}

local.isTopTier = local.tier.contestBond == 0;
Expand Down
6 changes: 3 additions & 3 deletions packages/protocol/contracts/verifiers/IVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ interface IVerifier {
/// @param _ctx The context of the proof verification.
/// @param _tran The transition to verify.
/// @param _proof The proof to verify.
function verifyProof(
Context calldata _ctx,
TaikoData.Transition calldata _tran,
function verifyProofs(
Context[] calldata _ctx,
TaikoData.Transition[] calldata _tran,
TaikoData.TierProof calldata _proof
)
external;
Expand Down
40 changes: 30 additions & 10 deletions packages/protocol/contracts/verifiers/SP1Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ contract SP1Verifier is EssentialContract, IVerifier {
/// @param trusted The block's assigned prover.
event ProgramTrusted(bytes32 programVKey, bool trusted);

error SP1_INVALID_INPUT();
error SP1_INVALID_PROGRAM_VKEY();
error SP1_INVALID_PROOF();

Expand All @@ -42,8 +43,8 @@ contract SP1Verifier is EssentialContract, IVerifier {

/// @inheritdoc IVerifier
function verifyProof(
Context calldata _ctx,
TaikoData.Transition calldata _tran,
Context[] calldata _ctx,
TaikoData.Transition[] calldata _tran,
TaikoData.TierProof calldata _proof
)
external
Expand All @@ -52,22 +53,41 @@ contract SP1Verifier is EssentialContract, IVerifier {
// Do not run proof verification to contest an existing proof
if (_ctx.isContesting) return;

// Avoid in-memory decoding, so in-place decode with slicing.
// e.g.: bytes32 programVKey = bytes32(_proof.data[0:32]);
if (!isProgramTrusted[bytes32(_proof.data[0:32])]) {
if (_ctx.length != _tran.length) {
revert SP1_INVALID_INPUT();
}

// Extract the necessary data
bytes32 aggregation_program = bytes32(_proof.data[0:32]);
bytes32 block_proving_program = bytes32(_proof.data[32:64]);
bytes memory proof = _proof.data[64:];

// Check if the aggregation program is trusted
if (!isProgramTrusted[aggregation_program]) {
revert SP1_INVALID_PROGRAM_VKEY();
}
// Check if the block proving program is trusted
if (!isProgramTrusted[block_proving_program]) {
revert SP1_INVALID_PROGRAM_VKEY();
}

// Need to be converted from bytes32 to bytes
bytes32 hashedPublicInput = LibPublicInput.hashPublicInputs(
_tran, address(this), address(0), _ctx.prover, _ctx.metaHash, taikoChainId()
);
// Collect public inputs
bytes32[] memory public_inputs = new bytes32[](_tran.length + 1);
// First public input is the block proving program key
public_inputs[0] = block_proving_program;
// All other inputs are the block program public inputs (a single 32 byte value)
for (uint i = 0; i < _tran.length; i++) {
// Need to be converted from bytes32 to bytes
public_inputs[i + 1] = sha256(abi.encodePacked(LibPublicInput.hashPublicInputs(
_tran, address(this), address(0), _ctx.prover, _ctx.metaHash, taikoChainId()
)));
}

// _proof.data[32:] is the succinct's proof position
(bool success,) = sp1RemoteVerifier().staticcall(
abi.encodeCall(
ISP1Verifier.verifyProof,
(bytes32(_proof.data[0:32]), abi.encode(hashedPublicInput), _proof.data[32:])
(block_proving_program, abi.encodePacked(public_inputs), proof)
)
);

Expand Down
39 changes: 28 additions & 11 deletions packages/protocol/contracts/verifiers/SgxVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ contract SgxVerifier is EssentialContract, IVerifier {
/// @param instance The address of the SGX instance.
event InstanceDeleted(uint256 indexed id, address indexed instance);

error SGX_INVALID_INPUT();
error SGX_ALREADY_ATTESTED();
error SGX_INVALID_ATTESTATION();
error SGX_INVALID_INSTANCE();
Expand Down Expand Up @@ -144,8 +145,8 @@ contract SgxVerifier is EssentialContract, IVerifier {

/// @inheritdoc IVerifier
function verifyProof(
Context calldata _ctx,
TaikoData.Transition calldata _tran,
Context[] calldata _ctx,
TaikoData.Transition[] calldata _tran,
TaikoData.TierProof calldata _proof
)
external
Expand All @@ -154,19 +155,35 @@ contract SgxVerifier is EssentialContract, IVerifier {
// Do not run proof verification to contest an existing proof
if (_ctx.isContesting) return;

if (_ctx.length != _tran.length) {
revert SGX_INVALID_INPUT();
}

// Size is: 89 bytes
// 4 bytes + 20 bytes + 65 bytes (signature) = 89
if (_proof.data.length != 89) revert SGX_INVALID_PROOF();
// 4 bytes + 20 bytes + 20 bytes + 65 bytes (signature) = 109
Copy link
Contributor

@dantaik dantaik Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the current SGX prover doesn't support multiple blocks, can we ensure the length of _ctx and _tran are both 1 for now so we can upgrade contracts ealier?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inputs into the verification are still changed so a bit tricky. If length is 1 we could use the old verification path, but then this would be a special case and proof aggregation on a single proof would not be valid. Seems easier if aggregation is always used even if there's only a single proof, otherwise we need multiple verification paths.

if (_proof.data.length != 109) revert SGX_INVALID_PROOF();

uint32 id = uint32(bytes4(_proof.data[:4]));
address newInstance = address(bytes20(_proof.data[4:24]));

address oldInstance = ECDSA.recover(
LibPublicInput.hashPublicInputs(
address oldInstance = address(bytes20(_proof.data[4:24]));
address newInstance = address(bytes20(_proof.data[24:44]));
bytes memory signature = _proof.data[44:];

// Collect public inputs
bytes32[] memory public_inputs = new bytes32[](_tran.length + 1);
// First public input is the block proving program key
public_inputs[0] = bytes32(oldInstance);
// All other inputs are the block program public inputs (a single 32 byte value)
for (uint i = 0; i < _tran.length; i++) {
// TODO: For now this assumes the new instance public key to remain the same
public_inputs[i + 1] = LibPublicInput.hashPublicInputs(
_tran, address(this), newInstance, _ctx.prover, _ctx.metaHash, taikoChainId()
),
_proof.data[24:]
);
);
}

require(oldInstance, ECDSA.recover(
keccak256(abi.encodePacked(public_inputs)),
signature
), "invalid signature");

if (!_isInstanceValid(id, oldInstance)) revert SGX_INVALID_INSTANCE();

Expand Down
Loading