Skip to content

Commit

Permalink
feat: implement verifyMembership
Browse files Browse the repository at this point in the history
  • Loading branch information
wenyuanhust committed Dec 7, 2023
1 parent d5df972 commit d115e22
Show file tree
Hide file tree
Showing 4 changed files with 317 additions and 5 deletions.
12 changes: 7 additions & 5 deletions contracts/clients/CkbClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ pragma solidity ^0.8.9;
import "../core/02-client/ILightClient.sol";
import "../core/02-client/IBCHeight.sol";
import "../proto/Client.sol";
import "./CkbProof.sol";

// MokkClient implements https://github.com/datachainlab/ibc-mock-client
// WARNING: This client is intended to be used for testing purpose. Therefore, it is not generally available in a production, except in a fully trusted environment.
contract CkbClient is ILightClient {
using CkbProof for *;
uint64 private constant MAX_UINT64 = 18446744073709551615;
constructor() {}

Expand Down Expand Up @@ -66,12 +68,12 @@ contract CkbClient is ILightClient {
Height.Data calldata,
uint64,
uint64,
bytes calldata,
bytes memory,
bytes calldata proof,
bytes memory,
bytes calldata
) external pure override returns (bool) {
return true;
bytes memory path,
bytes calldata value
) external override returns (bool) {
return CkbProof.verifyProof(proof, path, value);
}

/**
Expand Down
269 changes: 269 additions & 0 deletions contracts/clients/CkbProof.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.9;

import "../utils/Molecule.sol";
import "solidity-rlp/contracts/RLPReader.sol";

using Molecule for bytes;
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;

struct Proof {
uint32[] indices;
bytes32[] lemmas;
bytes32[] leaves;
}

struct VerifyProofPayload {
uint8 verifyType;
bytes32 transactionsRoot;
bytes32 witnessesRoot;
bytes32 rawTransactionsRoot;
Proof proof;
}

struct AxonObjectProof {
bytes ckbTransaction;
bytes32 blockHash;
VerifyProofPayload proofPayload;
}

struct CKBHeader {
uint32 version;
uint32 compactTarget;
uint64 timestamp;
uint64 number;
uint64 epch;
bytes32 parentHash;
bytes32 transactionsRoot;
bytes32 proposalsHash;
bytes32 extraHash;
bytes32 dao;
uint128 nonce;
bytes extension;
bytes32 blockHash;
}

// Define the MsgType enum
enum MsgType {
MsgClientCreate
}

// Define the CommitmentKV struct
struct CommitmentKV {
uint256 key;
uint256 value;
}

// Define the Envelope struct
struct Envelope {
MsgType msg_type;
CommitmentKV[] commitments;
bytes content;
}

contract CkbLightClient {
event GetHeaderEvent(CKBHeader);
event NotGetHeaderEvent();

function getHeader(bytes32 blockHash) public returns (CKBHeader memory) {
// axon_precompile_address(0x02)
address get_header_addr = address(0x0102);
(bool isSuccess, bytes memory res) = get_header_addr.staticcall(
abi.encode(blockHash)
);

CKBHeader memory header;
if (isSuccess) {
header = abi.decode(res, (CKBHeader));
emit GetHeaderEvent(header);
} else {
emit NotGetHeaderEvent();
}
return header;
}
}

// Define ckb blake2b
contract Blake2b {
function blake2b(bytes memory data) public view returns (bytes32) {
// axon_precompile_address(0x06)
address blake2b_addr = address(0x0106);
(bool isSuccess, bytes memory res) = blake2b_addr.staticcall(
abi.encode(data)
);

bytes32 hash;
if (isSuccess) {
hash = abi.decode(res, (bytes32));
}
return hash;
}
}

contract CkbMbt {
function verify(
VerifyProofPayload memory payload
) public view returns (bool) {
// axon_precompile_address(0x07)
address ckb_mbt_addr = address(0x0107);
(bool isSuccess, bytes memory res) = ckb_mbt_addr.staticcall(
abi.encode(payload)
);

uint8[] memory verify_success;
if (isSuccess) {
verify_success = abi.decode(res, (uint8[]));
} else {
return false;
}
return verify_success[0] == 1;
}
}

function parseProof(
bytes memory abiEncodedProof
) pure returns (AxonObjectProof memory) {
AxonObjectProof memory proof = abi.decode(
abiEncodedProof,
(AxonObjectProof)
);
return proof;
}

function calculateHashes(
bytes memory ckbTransaction
) view returns (bytes32 transactionHash, bytes32 witnessHash) {
// Calculate the hashes here
(uint256 offset, uint256 size) = ckbTransaction.readCKBTxRaw();
bytes memory raw_tx = new bytes(size);
for (uint i = 0; i < size; i++) {
raw_tx[i] = ckbTransaction[i + offset];
}

Blake2b blake2b;
return (blake2b.blake2b(raw_tx), blake2b.blake2b(ckbTransaction));
}

function decodeRlpEnvelope(
bytes memory rlpEncodedData
) pure returns (Envelope memory) {
RLPReader.RLPItem[] memory ls = rlpEncodedData.toRlpItem().toList();

// Decode the msg_type
MsgType msg_type = MsgType(ls[0].toUint());

// Decode the commitments
RLPReader.RLPItem[] memory commitmentsRlp = ls[1].toList();
CommitmentKV[] memory commitments;
for (uint i = 0; i < commitmentsRlp.length; i++) {
RLPReader.RLPItem[] memory kvRlp = commitmentsRlp[i].toList();
commitments[i] = CommitmentKV(kvRlp[0].toUint(), kvRlp[1].toUint());
}

// Decode the content
bytes memory content = ls[2].toBytes();

// Return the decoded Envelope
return Envelope(msg_type, commitments, content);
}

function parseCommitment(
bytes memory ckbTransaction
) pure returns (CommitmentKV[] memory) {
uint256 witness_count = ckbTransaction.readCKBTxWitnessCount();
uint8 output_type_index = 2;
(uint256 offset, uint256 size) = ckbTransaction.readCKBTxWitness(
uint8(witness_count),
output_type_index
);
bytes memory output_type_bytes = new bytes(size);
for (uint i = 0; i < size; i++) {
output_type_bytes[i] = ckbTransaction[i + offset];
}
Envelope memory witness_struct = decodeRlpEnvelope(output_type_bytes);
return witness_struct.commitments;
}

function verifyHashExist(
bytes32[] memory leaves,
bytes32 witnessHash
) pure returns (bool) {
bool isInLeaves = false;
for (uint i = 0; i < leaves.length; i++) {
if (leaves[i] == witnessHash) {
isInLeaves = true;
break;
}
}

return isInLeaves;
}

function isCommitInCommitments(
CommitmentKV[] memory commitments,
bytes memory key,
bytes calldata value
) pure returns (bool) {
uint256 keyHash = uint256(keccak256(key));
uint256 valueHash = uint256(keccak256(value));
for (uint i = 0; i < commitments.length; i++) {
if (
commitments[i].key == keyHash && commitments[i].value == valueHash
) {
return true;
}
}
return false;
}

library CkbProof {
function verifyProof(
bytes memory abiEncodedProof,
bytes memory path,
bytes calldata value
) public returns (bool) {
// Parse the proof from the abi encoded data
AxonObjectProof memory axonObjProof = parseProof(abiEncodedProof);

// Get the CKB header
CkbLightClient lightClient;
CKBHeader memory header = lightClient.getHeader(axonObjProof.blockHash);

// Calculate the transaction hash and witness hash
(, bytes32 witnessHash) = calculateHashes(axonObjProof.ckbTransaction);

// Check if the transaction hash or witness hash is in the leaves
if (
!verifyHashExist(
axonObjProof.proofPayload.proof.leaves,
witnessHash
)
) {
return false;
}

// Create the VerifyProofPayload
VerifyProofPayload memory payload = VerifyProofPayload({
verifyType: axonObjProof.proofPayload.verifyType,
transactionsRoot: header.transactionsRoot,
witnessesRoot: axonObjProof.proofPayload.witnessesRoot,
rawTransactionsRoot: axonObjProof.proofPayload.rawTransactionsRoot,
proof: axonObjProof.proofPayload.proof
});

// Verify the proof
CkbMbt ckbMbt;
if (!ckbMbt.verify(payload)) {
return false;
}

// Parse the commitment from the witness
CommitmentKV[] memory commitments = parseCommitment(
axonObjProof.ckbTransaction
);

// Check if the commitment path/value matches the provided path/value
return isCommitInCommitments(commitments, path, value);
}
}
1 change: 1 addition & 0 deletions contracts/utils/Molecule.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

library Molecule {
Expand Down
40 changes: 40 additions & 0 deletions test/verifyMembership.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const Molecule = artifacts.require("Molecule");
const CkbLightClient = artifacts.require('CkbLightClient');
const CkbMbt = artifacts.require('CkbMbt');
const Blake2b = artifacts.require('Blake2b');
const CkbProof = artifacts.require("CkbProof");

contract("CkbProof", (accounts) => {
it("should verify proof correctly", async () => {
const molecule = await Molecule.new();
console.log("molecule deployed on ", molecule.address);
await CkbProof.link(molecule.address);

const ckbLightClient = await CkbLightClient.new();
await CkbProof.link(ckbLightClient.address);
console.log("ckbLightClient deployed on ", ckbLightClient.address);

const ckbMbt = await CkbMbt.new();
console.log("ckbMbt deployed on ", ckbMbt.address);
await CkbProof.link(ckbMbt.address);

const blake2b = await Blake2b.new();
await CkbProof.link(blake2b.address);
console.log("blake2b deployed on ", blake2b.address);

// const ckbProofInstance = await CkbProof.new();
// const ckbProofInstance = await CkbProof.deployed();
// console.log("CkbProof deployed on ", ckbProofInstance.address);

// Replace with your actual values
// const abiEncodedProof = "0x888";
// const path = "0x000";
// const value = "0x77";

// const result = await ckbProofInstance.verifyMembership(abiEncodedProof, path, value);

// // Replace `expected` with the expected result
// const expected = true;
// assert.equal(result, expected, "The proof verification did not return the expected result");
});
});

0 comments on commit d115e22

Please sign in to comment.