├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .prettierrc.json ├── .solhint.json ├── Makefile ├── README.md ├── contracts ├── Migrations.sol ├── TendermintLightClient.sol ├── ibc │ ├── IBCChannel.sol │ ├── IBCClient.sol │ ├── IBCConnection.sol │ ├── IBCHandler.sol │ ├── IBCHost.sol │ ├── IBCIdentifier.sol │ ├── IBCModule.sol │ ├── IBCMsgs.sol │ └── IClient.sol ├── ics23 │ ├── ics23.sol │ ├── ics23Compress.sol │ ├── ics23Ops.sol │ └── ics23Proof.sol ├── mocks │ ├── Ed25519Mock.sol │ ├── MerkleTreeMock.sol │ ├── ProtoMock.sol │ ├── Secp256k1.sol │ └── TendermintMock.sol ├── package.json ├── proto │ ├── Channel.sol │ ├── Connection.sol │ ├── Encoder.sol │ ├── GoogleProtobufAny.sol │ ├── ProtoBufRuntime.sol │ ├── TendermintHelper.sol │ ├── TendermintLight.sol │ └── proofs.sol └── utils │ ├── Bytes.sol │ ├── Tendermint.sol │ └── crypto │ ├── Ed25519.sol │ ├── MerkleTree.sol │ └── Secp256k1.sol ├── migrations ├── 1_initial_migration.js ├── 2_deploy_contracts.js └── 3_initialize_contract.js ├── package-lock.json ├── package.json ├── proto ├── TendermintLight.proto ├── ibc │ ├── Channel.proto │ └── Connection.proto └── ics23 │ └── proofs.proto ├── scripts ├── confgen.js ├── deploy_with_stats.sh ├── fix_test_data.sh ├── getAccount.js ├── import_ibc.sh ├── import_ics23.sh ├── protobuf_compile.sh └── template │ └── contract.rs.tpl ├── test ├── Proto.test.js ├── TendermintLightClient.test.js ├── data │ ├── any.proto │ ├── header.28.signed_header.json │ ├── header.28.validator_set.json │ ├── header.29.signed_header.json │ ├── header.29.validator_set.json │ ├── header.30.signed_header.json │ ├── header.30.validator_set.json │ ├── header.8619996.signed_header.json │ ├── header.8619996.validator_set.json │ ├── header.8619997.signed_header.json │ ├── header.8619997.validator_set.json │ ├── header.8619998.signed_header.json │ └── header.8619998.validator_set.json ├── demo │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── abci.rs │ │ ├── consts.rs │ │ ├── eth.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── mod.rs │ │ ├── proto.rs │ │ ├── types.rs │ │ └── util.rs ├── lib.js └── utlis │ ├── Tendermint.test.js │ └── crypto │ ├── Ed25519.test.js │ ├── MerkleTree.test.js │ └── Secp256k1.test.js └── truffle-config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "standard" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 13, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/demo/target 2 | test/demo/src/consts.rs 3 | scripts/secret 4 | solidity-protobuf 5 | build 6 | node_modules 7 | ics23/ 8 | yui-ibc-solidity/ 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "no-inline-assembly": "off", 5 | "no-unused-vars": "error", 6 | "const-name-snakecase": "error", 7 | "contract-name-camelcase": "error", 8 | "event-name-camelcase": "error", 9 | "func-name-mixedcase": "error", 10 | "func-param-name-mixedcase": "error", 11 | "modifier-name-mixedcase": "error", 12 | "private-vars-leading-underscore": "error", 13 | "var-name-mixedcase": "error", 14 | "imports-on-top": "error" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-protoc build-protoc proto sol-lint js-lint sol-format import-ibc test demo 2 | 3 | clean-protoc: 4 | rm -rf ./solidity-protobuf 2>/dev/null 5 | 6 | build-protoc: | clean-protoc 7 | git clone https://github.com/mkaczanowski/solidity-protobuf 8 | 9 | proto: 10 | ./scripts/protobuf_compile.sh 11 | 12 | sol-lint: 13 | solhint 'contracts/{utils,mocks}/**/*.sol' 14 | 15 | js-lint: 16 | eslint test/utlis *.js 17 | 18 | sol-format: 19 | npx prettier --write 'contracts/{utils,mocks}/*.sol' 'contracts/proto/{Encoder.sol,TendermintHelper.sol}' 20 | 21 | import-ibc: 22 | ./scripts/import_ibc.sh 23 | 24 | config: 25 | export CONF_TPL="./test/demo/src/consts.rs:./scripts/template/contract.rs.tpl" && truffle exec ./scripts/confgen.js --network=$(NETWORK) 26 | 27 | test: 28 | truffle test --network tests 29 | 30 | demo: 31 | # gas-price: 0.5 gwei = 500000000 wei 32 | cd test/demo && cargo run -- --max-headers 3 --celo-gas-price 500000000 --celo-usd-price 5.20 33 | 34 | deploy: 35 | # gas-price: 0.5 gwei = 500000000 wei 36 | ./scripts/deploy_with_stats.sh 500000000 5.20 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tendermint-sol 2 | 3 | Solidity implementation of IBC (Inter-Blockchain Communication Protocol) compatible [Tendermint Light Client](https://github.com/cosmos/ibc/blob/master/spec/client/ics-007-tendermint-client/README.md) intended to run on [Celo EVM](https://celo.org/) (but not limited to it). 4 | 5 | Features: 6 | * supports both adjacent/non-adjacent (sequential/skipping) verification modes 7 | * supports Secp256k1 (via `ecrecover`) and Ed25519 (Celo EVM precompile) curves 8 | * supports [ics23 Merkle proofs](https://github.com/confio/ics23) 9 | * implements IBC interface via [yui-ibc-solidity](https://github.com/hyperledger-labs/yui-ibc-solidity) 10 | 11 | The Light Client comes in **two branches**: 12 | * main - the code is a very close copy of the [ibc-go light client](https://github.com/cosmos/ibc-go/tree/v2.0.0/modules/light-clients/07-tendermint) 13 | * optimized - the code has been sufficiently optimized to fit the Celo block gas limit (20M) while keeping all functionalities 14 | 15 | ## Light client in the nutshell 16 | The light client ingests the block headers coming from the full node and verifies them. Once the verification succeeds, the light client will update its `ConsensusState` with: 17 | * `next_validator_set_hash` - hash of the next validator set stored in the verified block header 18 | * `commitment_root` (e.g., app hash Merkle root) - also stored in the block header. 19 | 20 | We can verify the inclusion/exclusion in the Merkle tree with a valid proof and commitment root. For example, you can check if a transaction has been committed to transactions Merkle tree. But, how can you trust the commitment root? How do you know it has not been forged? 21 | 22 | Both values are members of the block header, so we need to check whether a header is valid. The verification requires: 23 | * block header 24 | * commit signatures 25 | * validator set (validator voting power and public key) 26 | * trusted validator set (non-adjacent verification) 27 | 28 | The core verification is quite simple. We build the `Canonical` structures with provided data, serialize them (with protobuf) and check via the cryptographic function (e.g., ed25519) if it matches the given signature. The validator set hash is checked against the `ConsensusState` prior signature verification to ensure the trust to validator set. 29 | 30 | The light client offers two verification modes: 31 | * adjcacent / sequential - block heights are sequential e.g., n, n+1, n+2, ... 32 | * non-adjacent / skipping - block heights aren't sequential e.g., n, n+1, ..., n+6, n+7 33 | 34 | Sequential mode is obvious, but when would one use the non-adjacent method? 35 | 36 | Syncing up headers after some time (e.g., relayer was down) might be expensive because the light client must process all missing headers up to the latest one. With the non-adjacent mode, we can quick-sync to the latest height, but it requires a `trusted_validator_set` to be passed on additionally. 37 | 38 | At the time of writing, the Cosmos Hub validator set contains 150 validators, so: 39 | * adjacent mode - requires 150 validator entries and 150 commit signatures 40 | * non-adjacent mode - requires 150 validator and 150 trusted validator entries + 150 commit signatures 41 | 42 | To learn more about the light client theory, see [this article](https://medium.com/tendermint/everything-you-need-to-know-about-the-tendermint-light-client-f80d03856f98) 43 | 44 | ## Performance analysis 45 | The benchmark aims to gauge the gas usage across the Tendermint Light Client contract and help out to identify potential optimization areas. 46 | 47 | There are a few segments/tests outlined: 48 | * all - test runs as is, no code is modified 49 | * no-precompile - the call to `Ed25519` precompile is commented out. (all - no-precompile = gas spent on precompile) 50 | * no-check-validity - the `checkValidity` call is commented out. This is the starting point for LC core logic. 51 | * unmarshal-header - unmarshal the header in the `CheckHeaderAndUpdateState` and return. 52 | * early-return - the `CheckHeaderAndUpdateState` method returns as quickly as possible (no deserialization, storage etc) 53 | 54 | Some of the segments can also be measured via unittests (see `test/.*js`). 55 | 56 | ### Setup 57 | * celo blockchain node (v1.3.2) 58 | * block headers relayed from CosmosHub public node 59 | * TM Light Client compiled with `0.8.2` solidity compiler 60 | * code checked out at: 61 | * vanilla (`8434ff68a7a90b1670a64ab36c7cdfc43a5ce1ad`) 62 | * optimized (`0a8acb90a8ef834e596538997859d6ee883dba97`) 63 | 64 | ### Running tests 65 | The Rust Demo program relays four headers from the Tendermint RPC node (e.g., cosmos hub) and calls light client code, particularly `CreateClient` and `CheckHeaderAndUpdateState`. In the non-adjacent mode, the second header is being skipped. 66 | 67 | ``` 68 | cd test/demo 69 | 70 | # adjacent mode 71 | cargo run -- --max-headers 4 --celo-gas-price 500000000 --celo-usd-price 5.20 --tendermint-url "https://rpc.atomscan.com" --gas 40000000 --celo-url http://localhost:8545 --from-height 8619996 72 | 73 | # non-adjacent mode 74 | cargo run -- --max-headers 4 --celo-gas-price 500000000 --celo-usd-price 5.20 --tendermint-url "https://rpc.atomscan.com" --gas 40000000 --celo-url http://localhost:8545 --from-height 8619996 --non-adjacent-mode 75 | ``` 76 | 77 | ### Vanilla Client (branch: main) 78 | 79 | header heights | mode | segment | Gas (init) | gas (h2) | gas (h3) | gas (h4) 80 | -----------------|--------------|-------------------|------------|----------|----------|---------- 81 | 8619996-8619999 | adjacent | all | 359531 | 16400033 | 16380490 | 16404617 82 | 8619996-8619999 | adjacent | no-precompile | 373215 | 16293500 | 16273960 | 16297936 83 | 8619996-8619999 | adjacent | no-check-validity | 373215 | 12634904 | 12616984 | 12638759 84 | 8619996-8619999 | adjacent | unmarshal-header | 359531 | 12258109 | 12286480 | 12308085 85 | 8619996-8619999 | adjacent | early-return | 373215 | 479499 | 524934 | 525989 86 | -- | -- | -- | -- | -- | -- | -- 87 | 8619996-8619999 | non-adjacent | all | 373215 | -- | 26734466 | 23511707 88 | 8619996-8619999 | non-adjacent | no-precompile | 359531 | -- | 26577975 | 23394015 89 | 8619996-8619999 | non-adjacent | no-check-validity | 359531 | -- | 19386277 | 19407358 90 | 8619996-8619999 | non-adjacent | unmarshal-header | 373215 | -- | 18992290 | 19059787 91 | 8619996-8619999 | non-adjacent | early-return | 359531 | -- | 640815 | 688152 92 | 93 | ----------- 94 | 95 | height | mode | base cost | serialization cost | check-validity cost | precompile cost | total | gas limit | gas usage 96 | ---------|--------------|------------|--------------------|---------------------|------------------|----------|-----------|------------ 97 | 8619997 | adjacent | 479499 | 11778610 | 3765129 | 106533 | 16400033 | 20M | 82.00 % 98 | -- | -- | 2.923 % | 71.820 % | 22.958 % | 0.6495 % | 100 % | -- | -- 99 | 8619998 | non-adjacent | 524934 | 18467356 | 7348189 | 156491 | 26734466 | 20M | 133.67 % 100 | -- | -- | 1.963 % | 69.07 % | 27.485 % | 0.585 % | 100 % | -- | -- 101 | 102 | 103 | 104 | ### Optimized Client (branch: optimized) 105 | 106 | header heights | mode | segment | Gas (init) | gas (h2) | gas (h3) | gas (h4) 107 | -----------------|--------------|-------------------|------------|----------|----------|---------- 108 | 8619996-8619999 | adjacent | all | 373191 | 12657290 | 12638571 | 12662331 109 | 8619996-8619999 | adjacent | no-precompile | 359507 | 12560073 | 12541355 | 12565112 110 | 8619996-8619999 | adjacent | no-check-validity | 373191 | 9627130 | 9609975 | 9631564 111 | 8619996-8619999 | adjacent | unmarshal-header | 373191 | 9364934 | 9347650 | 9369069 112 | 8619996-8619999 | adjacent | early-return | 359507 | 418250 | 463841 | 464895 113 | -- | -- | -- | -- | -- | -- | -- 114 | 8619996-8619999 | non-adjacent | all | 359507 | -- | 17976391 | 15550856 115 | 8619996-8619999 | non-adjacent | no-precompile | 373191 | -- | 17843584 | 15450647 116 | 8619996-8619999 | non-adjacent | no-check-validity | 359507 | -- | 12442497 | 12463389 117 | 8619996-8619999 | non-adjacent | unmarshal-header | 359507 | -- | 12175649 | 12196539 118 | 8619996-8619999 | non-adjacent | early-return | 373191 | -- | 518520 | 565852 119 | 120 | ----------- 121 | 122 | height | mode | base cost | serialization cost | check-validity cost | precompile cost | total | gas limit | gas usage 123 | ---------|--------------|------------|--------------------|---------------------|-------------------|----------|-----------|----------- 124 | 8619997 | adjacent | 418250 | 8946684 | 3030160 | 97217 | 12657290 | 20M | 63.28 % 125 | -- | -- | 3.30 % | 70.68 % | 23.94 % | 0.768 % | 100 % | -- | -- 126 | 8619998 | non-adjacent | 518520 | 11657129 | 5533894 | 132807 | 17976391 | 20M | 89.88 % 127 | -- | -- | 2.88 % | 64.846 % | 30.784 % | 0.738 % | 100 % | -- | -- 128 | 129 | 130 | ### Results overview 131 | By looking at the results, it's clear that: 132 | * protobuf deserialization costs are high. Umarshalling takes up to 70% in optimized client 133 | * core logic (check-validity) takes less than 30% 134 | * the signature verification via precompile is very cheap (compared to the rest) 135 | 136 | The `optimized` branch removes unused fields from `proto/TendermintLight.proto` and flattens some structures such as `PublicKey` to reduce deserialization costs. As shown, the gas usage in non-adjacent mode was lowered from `26734466` to `17976391` (89.88% of max allowed gas). 137 | 138 | The Light Client contract fits into Celo Blockchain, but running it may be expensive. 139 | 140 | Potential optimizations: 141 | * serialization - the input data doesn't need to be protobuf serialized, so: 142 | * further protobuf structure unification/nesting removal 143 | * alternative (simpler) serialization format may be evaluated e.g., RLP 144 | * custom serialization - for example `|validator_pub_key|voting_power|` can be stored as one byte array 145 | * try out [another protobuf compiler](https://github.com/celestiaorg/protobuf3-solidity) - maps, nested enums are not supported 146 | * removal of non-adjacent mode - if anticipated? 147 | 148 | NOTE: gas limit (20M) is the maximum allowed gas per block on Celo blockchain mainnet (2021-12-16) 149 | 150 | ## Quick Start 151 | ``` 152 | git clone https://github.com/ChorusOne/tendermint-sol.git 153 | cd tendermint-sol && git checkout optimized 154 | 155 | export NETWORK=celo 156 | 157 | # deploy with truffle 158 | make deploy 159 | 160 | # run demo program (local celo node must be running) 161 | cd test/demo 162 | cargo run -- --max-headers 4 --tendermint-url "https://rpc.atomscan.com" --gas 20000000 --celo-url http://localhost:8545 --from-height 8619996 163 | ``` 164 | 165 | ## Demo 166 | [![asciicast](https://asciinema.org/a/456622.svg)](https://asciinema.org/a/456622) 167 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | pragma solidity >=0.4.25 <=0.8.2; 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) _; 10 | } 11 | 12 | constructor() public { 13 | owner = msg.sender; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/ibc/IBCClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | 6 | import "./IClient.sol"; 7 | import "./IBCHost.sol"; 8 | import "./IBCMsgs.sol"; 9 | 10 | library IBCClient { 11 | 12 | /** 13 | * @dev createClient creates a new client state and populates it with a given consensus state 14 | */ 15 | function createClient(IBCHost host, IBCMsgs.MsgCreateClient calldata msg_) external { 16 | host.onlyIBCModule(); 17 | (, bool found) = getClientByType(host, msg_.clientType); 18 | require(found, "unregistered client type"); 19 | 20 | string memory clientId = host.generateClientIdentifier(msg_.clientType); 21 | host.setClientType(clientId, msg_.clientType); 22 | host.setClientState(clientId, msg_.clientStateBytes); 23 | host.setConsensusState(clientId, msg_.height, msg_.consensusStateBytes); 24 | host.setProcessedTime(clientId, msg_.height, block.timestamp); 25 | host.setProcessedHeight(clientId, msg_.height, block.number); 26 | } 27 | 28 | /** 29 | * @dev updateClient updates the consensus state and the state root from a provided header 30 | */ 31 | function updateClient(IBCHost host, IBCMsgs.MsgUpdateClient calldata msg_) external { 32 | host.onlyIBCModule(); 33 | bytes memory clientStateBytes; 34 | bytes memory consensusStateBytes; 35 | uint64 height; 36 | bool found; 37 | 38 | (clientStateBytes, found) = host.getClientState(msg_.clientId); 39 | require(found, "clientState not found"); 40 | 41 | (clientStateBytes, consensusStateBytes, height) = getClient(host, msg_.clientId).checkHeaderAndUpdateState(host, msg_.clientId, clientStateBytes, msg_.header); 42 | 43 | //// persist states //// 44 | host.setClientState(msg_.clientId, clientStateBytes); 45 | host.setConsensusState(msg_.clientId, height, consensusStateBytes); 46 | host.setProcessedTime(msg_.clientId, height, block.timestamp); 47 | host.setProcessedHeight(msg_.clientId, height, block.number); 48 | } 49 | 50 | // TODO implements 51 | function validateSelfClient(IBCHost host, bytes calldata clientStateBytes) external view returns (bool) { 52 | return true; 53 | } 54 | 55 | function registerClient(IBCHost host, string memory clientType, IClient client) public { 56 | host.onlyIBCModule(); 57 | host.setClientImpl(clientType, address(client)); 58 | } 59 | 60 | function getClient(IBCHost host, string memory clientId) public view returns (IClient) { 61 | (IClient clientImpl, bool found) = getClientByType(host, host.getClientType(clientId)); 62 | require(found, "clientImpl not found"); 63 | return clientImpl; 64 | } 65 | 66 | function getClientByType(IBCHost host, string memory clientType) internal view returns (IClient clientImpl, bool) { 67 | (address addr, bool found) = host.getClientImpl(clientType); 68 | if (!found) { 69 | return (clientImpl, false); 70 | } 71 | return (IClient(addr), true); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/ibc/IBCConnection.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | 6 | import "../proto/Connection.sol"; 7 | import "./IBCClient.sol"; 8 | import "./IBCMsgs.sol"; 9 | import "./IBCHost.sol"; 10 | 11 | library IBCConnection { 12 | 13 | string constant commitmentPrefix = "ibc"; 14 | 15 | /* Public functions */ 16 | 17 | // ConnOpenInit initialises a connection attempt on chain A. The generated connection identifier 18 | // is returned. 19 | function connectionOpenInit(IBCHost host, IBCMsgs.MsgConnectionOpenInit memory msg_) public returns (string memory) { 20 | host.onlyIBCModule(); 21 | ConnectionEnd.Data memory connection = ConnectionEnd.Data({ 22 | client_id: msg_.clientId, 23 | versions: getVersions(), 24 | state: ConnectionEnd.State.STATE_INIT, 25 | delay_period: msg_.delayPeriod, 26 | counterparty: msg_.counterparty 27 | }); 28 | string memory connectionId = host.generateConnectionIdentifier(); 29 | host.setConnection(connectionId, connection); 30 | return connectionId; 31 | } 32 | 33 | // ConnOpenTry relays notice of a connection attempt on chain A to chain B (this 34 | // code is executed on chain B). 35 | function connectionOpenTry( 36 | IBCHost host, 37 | IBCMsgs.MsgConnectionOpenTry memory msg_ 38 | ) public returns (string memory) { 39 | host.onlyIBCModule(); 40 | require(IBCClient.validateSelfClient(host, msg_.clientStateBytes), "failed to validate self client state"); 41 | require(msg_.counterpartyVersions.length > 0, "counterpartyVersions length must be greater than 0"); 42 | 43 | ConnectionEnd.Data memory connection = ConnectionEnd.Data({ 44 | client_id: msg_.clientId, 45 | versions: getVersions(), 46 | state: ConnectionEnd.State.STATE_TRYOPEN, 47 | delay_period: msg_.delayPeriod, 48 | counterparty: msg_.counterparty 49 | }); 50 | 51 | ConnectionEnd.Data memory expectedConnection = ConnectionEnd.Data({ 52 | client_id: msg_.counterparty.client_id, 53 | versions: msg_.counterpartyVersions, 54 | state: ConnectionEnd.State.STATE_INIT, 55 | delay_period: msg_.delayPeriod, 56 | counterparty: Counterparty.Data({ 57 | client_id: msg_.clientId, 58 | connection_id: "", 59 | prefix: MerklePrefix.Data({key_prefix: bytes(commitmentPrefix)}) 60 | }) 61 | }); 62 | 63 | require(verifyConnectionState(host, connection, msg_.proofHeight, msg_.proofInit, msg_.counterparty.connection_id, expectedConnection), "failed to verify connection state"); 64 | require(verifyClientState(host, connection, msg_.proofHeight, msg_.proofClient, msg_.clientStateBytes), "failed to verify clientState"); 65 | // TODO we should also verify a consensus state 66 | 67 | string memory connectionId = host.generateConnectionIdentifier(); 68 | host.setConnection(connectionId, connection); 69 | return connectionId; 70 | } 71 | 72 | function connectionOpenAck( 73 | IBCHost host, 74 | IBCMsgs.MsgConnectionOpenAck memory msg_ 75 | ) public { 76 | host.onlyIBCModule(); 77 | (ConnectionEnd.Data memory connection, bool found) = host.getConnection(msg_.connectionId); 78 | require(found, "connection not found"); 79 | 80 | if (connection.state != ConnectionEnd.State.STATE_INIT && connection.state != ConnectionEnd.State.STATE_TRYOPEN) { 81 | revert("connection state is not INIT or TRYOPEN"); 82 | } else if (connection.state == ConnectionEnd.State.STATE_INIT && !isSupportedVersion(msg_.version)) { 83 | revert("connection state is in INIT but the provided version is not supported"); 84 | } else if (connection.state == ConnectionEnd.State.STATE_TRYOPEN && (connection.versions.length != 1 || !isEqualVersion(connection.versions[0], msg_.version))) { 85 | revert("connection state is in TRYOPEN but the provided version is not set in the previous connection versions"); 86 | } 87 | 88 | require(IBCClient.validateSelfClient(host, msg_.clientStateBytes), "failed to validate self client state"); 89 | 90 | Counterparty.Data memory expectedCounterparty = Counterparty.Data({ 91 | client_id: connection.client_id, 92 | connection_id: msg_.connectionId, 93 | prefix: MerklePrefix.Data({key_prefix: bytes(commitmentPrefix)}) 94 | }); 95 | 96 | ConnectionEnd.Data memory expectedConnection = ConnectionEnd.Data({ 97 | client_id: connection.counterparty.client_id, 98 | versions: makeVersionArray(msg_.version), 99 | state: ConnectionEnd.State.STATE_TRYOPEN, 100 | delay_period: connection.delay_period, 101 | counterparty: expectedCounterparty 102 | }); 103 | 104 | require(verifyConnectionState(host, connection, msg_.proofHeight, msg_.proofTry, msg_.counterpartyConnectionID, expectedConnection), "failed to verify connection state"); 105 | require(verifyClientState(host, connection, msg_.proofHeight, msg_.proofClient, msg_.clientStateBytes), "failed to verify clientState"); 106 | // TODO we should also verify a consensus state 107 | 108 | connection.state = ConnectionEnd.State.STATE_OPEN; 109 | connection.versions = expectedConnection.versions; 110 | connection.counterparty.connection_id = msg_.counterpartyConnectionID; 111 | host.setConnection(msg_.connectionId, connection); 112 | } 113 | 114 | function connectionOpenConfirm( 115 | IBCHost host, 116 | IBCMsgs.MsgConnectionOpenConfirm memory msg_ 117 | ) public { 118 | host.onlyIBCModule(); 119 | (ConnectionEnd.Data memory connection, bool found) = host.getConnection(msg_.connectionId); 120 | require(found, "connection not found"); 121 | 122 | require(connection.state == ConnectionEnd.State.STATE_TRYOPEN, "connection state is not TRYOPEN"); 123 | 124 | Counterparty.Data memory expectedCounterparty = Counterparty.Data({ 125 | client_id: connection.client_id, 126 | connection_id: msg_.connectionId, 127 | prefix: MerklePrefix.Data({key_prefix: bytes(commitmentPrefix)}) 128 | }); 129 | 130 | ConnectionEnd.Data memory expectedConnection = ConnectionEnd.Data({ 131 | client_id: connection.counterparty.client_id, 132 | versions: connection.versions, 133 | state: ConnectionEnd.State.STATE_OPEN, 134 | delay_period: connection.delay_period, 135 | counterparty: expectedCounterparty 136 | }); 137 | 138 | require(verifyConnectionState(host, connection, msg_.proofHeight, msg_.proofAck, connection.counterparty.connection_id, expectedConnection), "failed to verify connection state"); 139 | 140 | connection.state = ConnectionEnd.State.STATE_OPEN; 141 | host.setConnection(msg_.connectionId, connection); 142 | } 143 | 144 | // Verification functions 145 | 146 | function verifyConnectionState(IBCHost host, ConnectionEnd.Data memory connection, uint64 height, bytes memory proof, string memory connectionId, ConnectionEnd.Data memory counterpartyConnection) internal returns (bool) { 147 | return IBCClient.getClient(host, connection.client_id).verifyConnectionState(host, connection.client_id, height, connection.counterparty.prefix.key_prefix, proof, connectionId, ConnectionEnd.encode(counterpartyConnection)); 148 | } 149 | 150 | function verifyClientState(IBCHost host, ConnectionEnd.Data memory connection, uint64 height, bytes memory proof, bytes memory clientStateBytes) internal returns (bool) { 151 | return IBCClient.getClient(host, connection.client_id).verifyClientState(host, connection.client_id, height, connection.counterparty.prefix.key_prefix, connection.counterparty.client_id, proof, clientStateBytes); 152 | } 153 | 154 | function verifyClientConsensusStateWithConnection(IBCHost host, ConnectionEnd.Data memory connection, uint64 height, uint64 consensusHeight, bytes memory proof, bytes memory consensusStateBytes) internal returns (bool) { 155 | return IBCClient.getClient(host, connection.client_id).verifyClientConsensusState(host, connection.client_id, height, connection.counterparty.client_id, consensusHeight, connection.counterparty.prefix.key_prefix, proof, consensusStateBytes); 156 | } 157 | 158 | function verifyChannelState(IBCHost host, ConnectionEnd.Data memory connection, uint64 height, bytes memory proof, string memory portId, string memory channelId, bytes memory channelBytes) public returns (bool) { 159 | return IBCClient.getClient(host, connection.client_id).verifyChannelState(host, connection.client_id, height, connection.counterparty.prefix.key_prefix, proof, portId, channelId, channelBytes); 160 | } 161 | 162 | function verifyPacketCommitment(IBCHost host, ConnectionEnd.Data memory connection, uint64 height, bytes memory proof, string memory portId, string memory channelId, uint64 sequence, bytes32 commitmentBytes) public returns (bool) { 163 | uint64 blockDelay = calcBlockDelay(host, connection.delay_period); 164 | return IBCClient.getClient(host, connection.client_id).verifyPacketCommitment(host, connection.client_id, height, connection.delay_period, blockDelay, connection.counterparty.prefix.key_prefix, proof, portId, channelId, sequence, commitmentBytes); 165 | } 166 | 167 | function verifyPacketAcknowledgement(IBCHost host, ConnectionEnd.Data memory connection, uint64 height, bytes memory proof, string memory portId, string memory channelId, uint64 sequence, bytes memory acknowledgement) public returns (bool) { 168 | uint64 blockDelay = calcBlockDelay(host, connection.delay_period); 169 | return IBCClient.getClient(host, connection.client_id).verifyPacketAcknowledgement(host, connection.client_id, height, connection.delay_period, blockDelay, connection.counterparty.prefix.key_prefix, proof, portId, channelId, sequence, acknowledgement); 170 | } 171 | 172 | // Internal functions 173 | 174 | function getVersions() internal pure returns (Version.Data[] memory) { 175 | Version.Data[] memory versions = new Version.Data[](1); 176 | string[] memory features = new string[](2); 177 | features[0] = "ORDER_ORDERED"; 178 | features[1] = "ORDER_UNORDERED"; 179 | versions[0] = Version.Data({ 180 | identifier: "1", 181 | features: features 182 | }); 183 | return versions; 184 | } 185 | 186 | function calcBlockDelay(IBCHost host, uint64 timeDelay) private view returns (uint64) { 187 | uint64 blockDelay = 0; 188 | uint64 expectedTimePerBlock = host.getExpectedTimePerBlock(); 189 | if (expectedTimePerBlock != 0) { 190 | blockDelay = (timeDelay + expectedTimePerBlock - 1) / expectedTimePerBlock; 191 | } 192 | return blockDelay; 193 | } 194 | 195 | // TODO implements 196 | function isSupportedVersion(Version.Data memory proposedVersion) internal pure returns (bool) { 197 | return true; 198 | } 199 | 200 | function isEqualVersion(Version.Data memory a, Version.Data memory b) internal pure returns (bool) { 201 | return keccak256(Version.encode(a)) == keccak256(Version.encode(b)); 202 | } 203 | 204 | function makeVersionArray(Version.Data memory version) internal pure returns (Version.Data[] memory ret) { 205 | ret = new Version.Data[](1); 206 | ret[0] = version; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /contracts/ibc/IBCHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | 6 | import "./IClient.sol"; 7 | import "./IBCClient.sol"; 8 | import "./IBCChannel.sol"; 9 | import "./IBCModule.sol"; 10 | import "./IBCMsgs.sol"; 11 | import "./IBCIdentifier.sol"; 12 | import "../proto/Channel.sol"; 13 | 14 | contract IBCHandler { 15 | 16 | address owner; 17 | IBCHost host; 18 | 19 | /// Event definitions /// 20 | event SendPacket(Packet.Data packet); 21 | event RecvPacket(Packet.Data packet); 22 | event WriteAcknowledgement(string destinationPortId, string destinationChannel, uint64 sequence, bytes acknowledgement); 23 | event AcknowledgePacket(Packet.Data packet, bytes acknowledgement); 24 | 25 | constructor(IBCHost host_) public { 26 | owner = msg.sender; 27 | host = host_; 28 | } 29 | 30 | function getHostAddress() external view returns (address) { 31 | return address(host); 32 | } 33 | 34 | function registerClient(string calldata clientType, IClient client) external { 35 | require(msg.sender == owner); 36 | return IBCClient.registerClient(host, clientType, client); 37 | } 38 | 39 | /// Handler interface implementations /// 40 | 41 | function createClient(IBCMsgs.MsgCreateClient calldata msg_) external { 42 | return IBCClient.createClient(host, msg_); 43 | } 44 | 45 | function updateClient(IBCMsgs.MsgUpdateClient calldata msg_) external { 46 | return IBCClient.updateClient(host, msg_); 47 | } 48 | 49 | function connectionOpenInit(IBCMsgs.MsgConnectionOpenInit memory msg_) public returns (string memory) { 50 | return IBCConnection.connectionOpenInit(host, msg_); 51 | } 52 | 53 | function connectionOpenTry(IBCMsgs.MsgConnectionOpenTry memory msg_) public returns (string memory) { 54 | return IBCConnection.connectionOpenTry(host, msg_); 55 | } 56 | 57 | function connectionOpenAck(IBCMsgs.MsgConnectionOpenAck memory msg_) public { 58 | return IBCConnection.connectionOpenAck(host, msg_); 59 | } 60 | 61 | function connectionOpenConfirm(IBCMsgs.MsgConnectionOpenConfirm memory msg_) public { 62 | return IBCConnection.connectionOpenConfirm(host, msg_); 63 | } 64 | 65 | function channelOpenInit(IBCMsgs.MsgChannelOpenInit memory msg_) public returns (string memory) { 66 | string memory channelId = IBCChannel.channelOpenInit(host, msg_); 67 | IModuleCallbacks module = lookupModuleByPortId(msg_.portId); 68 | module.onChanOpenInit( 69 | msg_.channel.ordering, 70 | msg_.channel.connection_hops, 71 | msg_.portId, 72 | channelId, 73 | msg_.channel.counterparty, 74 | msg_.channel.version 75 | ); 76 | host.claimCapability(IBCIdentifier.channelCapabilityPath(msg_.portId, channelId), address(module)); 77 | return channelId; 78 | } 79 | 80 | function channelOpenTry(IBCMsgs.MsgChannelOpenTry memory msg_) public returns (string memory) { 81 | string memory channelId = IBCChannel.channelOpenTry(host, msg_); 82 | IModuleCallbacks module = lookupModuleByPortId(msg_.portId); 83 | module.onChanOpenTry( 84 | msg_.channel.ordering, 85 | msg_.channel.connection_hops, 86 | msg_.portId, 87 | channelId, 88 | msg_.channel.counterparty, 89 | msg_.channel.version, 90 | msg_.counterpartyVersion 91 | ); 92 | host.claimCapability(IBCIdentifier.channelCapabilityPath(msg_.portId, channelId), address(module)); 93 | return channelId; 94 | } 95 | 96 | function channelOpenAck(IBCMsgs.MsgChannelOpenAck memory msg_) public { 97 | IBCChannel.channelOpenAck(host, msg_); 98 | lookupModuleByPortId(msg_.portId).onChanOpenAck(msg_.portId, msg_.channelId, msg_.counterpartyVersion); 99 | } 100 | 101 | function channelOpenConfirm(IBCMsgs.MsgChannelOpenConfirm memory msg_) public { 102 | IBCChannel.channelOpenConfirm(host, msg_); 103 | lookupModuleByPortId(msg_.portId).onChanOpenConfirm(msg_.portId, msg_.channelId); 104 | } 105 | 106 | function channelCloseInit(IBCMsgs.MsgChannelCloseInit memory msg_) public { 107 | IBCChannel.channelCloseInit(host, msg_); 108 | lookupModuleByPortId(msg_.portId).onChanCloseInit(msg_.portId, msg_.channelId); 109 | } 110 | 111 | function channelCloseConfirm(IBCMsgs.MsgChannelCloseConfirm memory msg_) public { 112 | IBCChannel.channelCloseConfirm(host, msg_); 113 | lookupModuleByPortId(msg_.portId).onChanCloseConfirm(msg_.portId, msg_.channelId); 114 | } 115 | 116 | function sendPacket(Packet.Data calldata packet) external { 117 | require(host.authenticateCapability( 118 | IBCIdentifier.channelCapabilityPath(packet.source_port, packet.source_channel), 119 | msg.sender 120 | )); 121 | IBCChannel.sendPacket(host, packet); 122 | emit SendPacket(packet); 123 | } 124 | 125 | function recvPacket(IBCMsgs.MsgPacketRecv calldata msg_) external returns (bytes memory acknowledgement) { 126 | IModuleCallbacks module = lookupModuleByChannel(msg_.packet.destination_port, msg_.packet.destination_channel); 127 | acknowledgement = module.onRecvPacket(msg_.packet); 128 | IBCChannel.recvPacket(host, msg_); 129 | if (acknowledgement.length > 0) { 130 | IBCChannel.writeAcknowledgement(host, msg_.packet.destination_port, msg_.packet.destination_channel, msg_.packet.sequence, acknowledgement); 131 | emit WriteAcknowledgement(msg_.packet.destination_port, msg_.packet.destination_channel, msg_.packet.sequence, acknowledgement); 132 | } 133 | emit RecvPacket(msg_.packet); 134 | return acknowledgement; 135 | } 136 | 137 | function writeAcknowledgement(string calldata destinationPortId, string calldata destinationChannel, uint64 sequence, bytes calldata acknowledgement) external { 138 | require(host.authenticateCapability( 139 | IBCIdentifier.channelCapabilityPath(destinationPortId, destinationChannel), 140 | msg.sender 141 | )); 142 | IBCChannel.writeAcknowledgement(host, destinationPortId, destinationChannel, sequence, acknowledgement); 143 | emit WriteAcknowledgement(destinationPortId, destinationChannel, sequence, acknowledgement); 144 | } 145 | 146 | function acknowledgePacket(IBCMsgs.MsgPacketAcknowledgement calldata msg_) external { 147 | IModuleCallbacks module = lookupModuleByChannel(msg_.packet.source_port, msg_.packet.source_channel); 148 | module.onAcknowledgementPacket(msg_.packet, msg_.acknowledgement); 149 | IBCChannel.acknowledgePacket(host, msg_); 150 | emit AcknowledgePacket(msg_.packet, msg_.acknowledgement); 151 | } 152 | 153 | function bindPort(string memory portId, address moduleAddress) public { 154 | onlyOwner(); 155 | host.claimCapability(IBCIdentifier.portCapabilityPath(portId), moduleAddress); 156 | } 157 | 158 | function setExpectedTimePerBlock(uint64 expectedTimePerBlock_) external { 159 | // TODO: consider better authn/authz for this operation 160 | onlyOwner(); 161 | host.setExpectedTimePerBlock(expectedTimePerBlock_); 162 | } 163 | 164 | function lookupModuleByPortId(string memory portId) internal view returns (IModuleCallbacks) { 165 | (address module, bool found) = host.getModuleOwner(IBCIdentifier.portCapabilityPath(portId)); 166 | require(found); 167 | return IModuleCallbacks(module); 168 | } 169 | 170 | function lookupModuleByChannel(string memory portId, string memory channelId) internal view returns (IModuleCallbacks) { 171 | (address module, bool found) = host.getModuleOwner(IBCIdentifier.channelCapabilityPath(portId, channelId)); 172 | require(found); 173 | return IModuleCallbacks(module); 174 | } 175 | 176 | function onlyOwner() internal view { 177 | require(msg.sender == owner); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /contracts/ibc/IBCIdentifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | library IBCIdentifier { 6 | 7 | // constant values 8 | 9 | uint256 constant commitmentSlot = 0; 10 | uint8 constant clientPrefix = 0; 11 | uint8 constant consensusStatePrefix = 1; 12 | uint8 constant connectionPrefix = 2; 13 | uint8 constant channelPrefix = 3; 14 | uint8 constant packetPrefix = 4; 15 | uint8 constant packetAckPrefix = 5; 16 | 17 | // Commitment key generator 18 | 19 | function clientCommitmentKey(string memory clientId) public pure returns (bytes32) { 20 | return keccak256(abi.encodePacked(clientPrefix, clientId)); 21 | } 22 | 23 | function consensusCommitmentKey(string memory clientId, uint64 height) public pure returns (bytes32) { 24 | return keccak256(abi.encodePacked(consensusStatePrefix, clientId, "/", height)); 25 | } 26 | 27 | function connectionCommitmentKey(string memory connectionId) public pure returns (bytes32) { 28 | return keccak256(abi.encodePacked(connectionPrefix, connectionId)); 29 | } 30 | 31 | function channelCommitmentKey(string memory portId, string memory channelId) public pure returns (bytes32) { 32 | return keccak256(abi.encodePacked(channelPrefix, portId, "/", channelId)); 33 | } 34 | 35 | function packetCommitmentKey(string memory portId, string memory channelId, uint64 sequence) public pure returns (bytes32) { 36 | return keccak256(abi.encodePacked(packetPrefix, portId, "/", channelId, "/", sequence)); 37 | } 38 | 39 | function packetAcknowledgementCommitmentKey(string memory portId, string memory channelId, uint64 sequence) public pure returns (bytes32) { 40 | return keccak256(abi.encodePacked(packetAckPrefix, portId, "/", channelId, "/", sequence)); 41 | } 42 | 43 | // Slot calculator 44 | 45 | function clientStateCommitmentSlot(string calldata clientId) external pure returns (bytes32) { 46 | return keccak256(abi.encodePacked(clientCommitmentKey(clientId), commitmentSlot)); 47 | } 48 | 49 | function consensusStateCommitmentSlot(string calldata clientId, uint64 height) external pure returns (bytes32) { 50 | return keccak256(abi.encodePacked(consensusCommitmentKey(clientId, height), commitmentSlot)); 51 | } 52 | 53 | function connectionCommitmentSlot(string calldata connectionId) external pure returns (bytes32) { 54 | return keccak256(abi.encodePacked(connectionCommitmentKey(connectionId), commitmentSlot)); 55 | } 56 | 57 | function channelCommitmentSlot(string calldata portId, string calldata channelId) external pure returns (bytes32) { 58 | return keccak256(abi.encodePacked(channelCommitmentKey(portId, channelId), commitmentSlot)); 59 | } 60 | 61 | function packetCommitmentSlot(string calldata portId, string calldata channelId, uint64 sequence) external pure returns (bytes32) { 62 | return keccak256(abi.encodePacked(packetCommitmentKey(portId, channelId, sequence), commitmentSlot)); 63 | } 64 | 65 | function packetAcknowledgementCommitmentSlot(string calldata portId, string calldata channelId, uint64 sequence) external pure returns (bytes32) { 66 | return keccak256(abi.encodePacked(packetAcknowledgementCommitmentKey(portId, channelId, sequence), commitmentSlot)); 67 | } 68 | 69 | // CapabilityPath 70 | 71 | function portCapabilityPath(string calldata portId) external pure returns (bytes memory) { 72 | return abi.encodePacked(portId); 73 | } 74 | 75 | function channelCapabilityPath(string calldata portId, string calldata channelId) external pure returns (bytes memory) { 76 | return abi.encodePacked(portId, "/", channelId); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/ibc/IBCModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | 6 | import "../proto/Channel.sol"; 7 | 8 | interface IModuleCallbacks { 9 | function onChanOpenInit(Channel.Order, string[] calldata connectionHops, string calldata portId, string calldata channelId, ChannelCounterparty.Data calldata counterparty, string calldata version) external; 10 | function onChanOpenTry(Channel.Order, string[] calldata connectionHops, string calldata portId, string calldata channelId, ChannelCounterparty.Data calldata counterparty, string calldata version, string calldata counterpartyVersion) external; 11 | function onChanOpenAck(string calldata portId, string calldata channelId, string calldata counterpartyVersion) external; 12 | function onChanOpenConfirm(string calldata portId, string calldata channelId) external; 13 | function onChanCloseInit(string calldata portId, string calldata channelId) external; 14 | function onChanCloseConfirm(string calldata portId, string calldata channelId) external; 15 | 16 | function onRecvPacket(Packet.Data calldata) external returns(bytes memory); 17 | function onAcknowledgementPacket(Packet.Data calldata, bytes calldata acknowledgement) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/ibc/IBCMsgs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | 6 | import "../proto/Connection.sol"; 7 | import "../proto/Channel.sol"; 8 | 9 | /* 10 | IBCMsgs defines Datagrams in ics-026. 11 | */ 12 | library IBCMsgs { 13 | 14 | /// Client /// 15 | 16 | struct MsgCreateClient { 17 | string clientType; 18 | uint64 height; 19 | bytes clientStateBytes; 20 | bytes consensusStateBytes; 21 | } 22 | 23 | struct MsgUpdateClient { 24 | string clientId; 25 | bytes header; 26 | } 27 | 28 | /// Connection handshake /// 29 | 30 | struct MsgConnectionOpenInit { 31 | string clientId; 32 | Counterparty.Data counterparty; 33 | uint64 delayPeriod; 34 | } 35 | 36 | struct MsgConnectionOpenTry { 37 | string previousConnectionId; 38 | Counterparty.Data counterparty; // counterpartyConnectionIdentifier, counterpartyPrefix and counterpartyClientIdentifier 39 | uint64 delayPeriod; 40 | string clientId; // clientID of chainA 41 | bytes clientStateBytes; // clientState that chainA has for chainB 42 | Version.Data[] counterpartyVersions; // supported versions of chain A 43 | bytes proofInit; // proof that chainA stored connectionEnd in state (on ConnOpenInit) 44 | bytes proofClient; // proof that chainA stored a light client of chainB 45 | bytes proofConsensus; // proof that chainA stored chainB's consensus state at consensus height 46 | uint64 proofHeight; // height at which relayer constructs proof of A storing connectionEnd in state 47 | uint64 consensusHeight; // latest height of chain B which chain A has stored in its chain B client 48 | } 49 | 50 | struct MsgConnectionOpenAck { 51 | string connectionId; 52 | bytes clientStateBytes; // client state for chainA on chainB 53 | Version.Data version; // version that ChainB chose in ConnOpenTry 54 | string counterpartyConnectionID; 55 | bytes proofTry; // proof that connectionEnd was added to ChainB state in ConnOpenTry 56 | bytes proofClient; // proof of client state on chainB for chainA 57 | bytes proofConsensus; // proof that chainB has stored ConsensusState of chainA on its client 58 | uint64 proofHeight; // height that relayer constructed proofTry 59 | uint64 consensusHeight; // latest height of chainA that chainB has stored on its chainA client 60 | } 61 | 62 | struct MsgConnectionOpenConfirm { 63 | string connectionId; 64 | bytes proofAck; 65 | uint64 proofHeight; 66 | } 67 | 68 | /// Channel handshake /// 69 | 70 | struct MsgChannelOpenInit { 71 | string portId; 72 | Channel.Data channel; 73 | } 74 | 75 | struct MsgChannelOpenTry { 76 | string portId; 77 | string previousChannelId; 78 | Channel.Data channel; 79 | string counterpartyVersion; 80 | bytes proofInit; 81 | uint64 proofHeight; 82 | } 83 | 84 | struct MsgChannelOpenAck { 85 | string portId; 86 | string channelId; 87 | string counterpartyVersion; 88 | string counterpartyChannelId; 89 | bytes proofTry; 90 | uint64 proofHeight; 91 | } 92 | 93 | struct MsgChannelOpenConfirm { 94 | string portId; 95 | string channelId; 96 | bytes proofAck; 97 | uint64 proofHeight; 98 | } 99 | 100 | /// Channel closing /// 101 | 102 | struct MsgChannelCloseInit { 103 | string portId; 104 | string channelId; 105 | } 106 | 107 | struct MsgChannelCloseConfirm { 108 | string portId; 109 | string channelId; 110 | bytes proofInit; 111 | uint64 proofHeight; 112 | } 113 | 114 | /// Packet /// 115 | 116 | struct MsgPacketRecv { 117 | Packet.Data packet; 118 | bytes proof; 119 | uint64 proofHeight; 120 | } 121 | 122 | struct MsgPacketAcknowledgement { 123 | Packet.Data packet; 124 | bytes acknowledgement; 125 | bytes proof; 126 | uint64 proofHeight; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /contracts/ibc/IClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/hyperledger-labs/yui-ibc-solidity 3 | pragma solidity ^0.8.2; 4 | 5 | import "./IBCHost.sol"; 6 | 7 | interface IClient { 8 | 9 | /** 10 | * @dev getTimestampAtHeight returns the timestamp of the consensus state at the given height. 11 | */ 12 | function getTimestampAtHeight( 13 | IBCHost host, 14 | string calldata clientId, 15 | uint64 height 16 | ) external view returns (uint64, bool); 17 | 18 | function getLatestHeight( 19 | IBCHost host, 20 | string calldata clientId 21 | ) external view returns (uint64, bool); 22 | 23 | function checkHeaderAndUpdateState( 24 | IBCHost host, 25 | string calldata clientId, 26 | bytes calldata clientStateBytes, 27 | bytes calldata headerBytes 28 | ) external view returns (bytes memory newClientStateBytes, bytes memory newConsensusStateBytes, uint64 height); 29 | 30 | function verifyClientState( 31 | IBCHost host, 32 | string calldata clientId, 33 | uint64 height, 34 | bytes calldata prefix, 35 | string calldata counterpartyClientIdentifier, 36 | bytes calldata proof, 37 | bytes calldata clientStateBytes // serialized with pb 38 | ) external returns (bool); 39 | 40 | function verifyClientConsensusState( 41 | IBCHost host, 42 | string calldata clientId, 43 | uint64 height, 44 | string calldata counterpartyClientIdentifier, 45 | uint64 consensusHeight, 46 | bytes calldata prefix, 47 | bytes calldata proof, 48 | bytes calldata consensusStateBytes // serialized with pb 49 | ) external returns (bool); 50 | 51 | function verifyConnectionState( 52 | IBCHost host, 53 | string calldata clientId, 54 | uint64 height, 55 | bytes calldata prefix, 56 | bytes calldata proof, 57 | string calldata connectionId, 58 | bytes calldata connectionBytes // serialized with pb 59 | ) external returns (bool); 60 | 61 | function verifyChannelState( 62 | IBCHost host, 63 | string calldata clientId, 64 | uint64 height, 65 | bytes calldata prefix, 66 | bytes calldata proof, 67 | string calldata portId, 68 | string calldata channelId, 69 | bytes calldata channelBytes // serialized with pb 70 | ) external returns (bool); 71 | 72 | function verifyPacketCommitment( 73 | IBCHost host, 74 | string calldata clientId, 75 | uint64 height, 76 | uint64 delayPeriodTime, 77 | uint64 delayPeriodBlocks, 78 | bytes calldata prefix, 79 | bytes calldata proof, 80 | string calldata portId, 81 | string calldata channelId, 82 | uint64 sequence, 83 | bytes32 commitmentBytes // serialized with pb 84 | ) external returns (bool); 85 | 86 | function verifyPacketAcknowledgement( 87 | IBCHost host, 88 | string calldata clientId, 89 | uint64 height, 90 | uint64 delayPeriodTime, 91 | uint64 delayPeriodBlocks, 92 | bytes calldata prefix, 93 | bytes calldata proof, 94 | string calldata portId, 95 | string calldata channelId, 96 | uint64 sequence, 97 | bytes calldata acknowledgement // serialized with pb 98 | ) external returns (bool); 99 | } 100 | -------------------------------------------------------------------------------- /contracts/ics23/ics23.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/ChorusOne/ics23/tree/giulio/solidity 3 | // SPDX-License-Identifier: Apache-2.0 4 | pragma solidity ^0.8.2; 5 | 6 | import {BatchProof, CompressedBatchProof, CommitmentProof, ProofSpec, ExistenceProof, NonExistenceProof} from "../proto/proofs.sol"; 7 | import {Compress} from "./ics23Compress.sol"; 8 | import {Proof} from "./ics23Proof.sol"; 9 | import {Ops} from "./ics23Ops.sol"; 10 | import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; 11 | 12 | library Ics23 { 13 | 14 | enum VerifyMembershipError { 15 | None, 16 | ExistenceProofIsNil, 17 | ProofVerify 18 | } 19 | // verifyMembership, throws an exception in case anything goes wrong 20 | function verifyMembership(ProofSpec.Data memory spec, bytes memory commitmentRoot, CommitmentProof.Data memory proof, bytes memory key, bytes memory value) internal pure returns(VerifyMembershipError){ 21 | CommitmentProof.Data memory decoProof = Compress.decompress(proof); 22 | ExistenceProof.Data memory exiProof = getExistProofForKey(decoProof, key); 23 | //require(ExistenceProof.isNil(exiProof) == false); // dev: getExistProofForKey not available 24 | if (ExistenceProof.isNil(exiProof)) return VerifyMembershipError.ExistenceProofIsNil; 25 | Proof.VerifyExistenceError vCode = Proof.verify(exiProof, spec, commitmentRoot, key, value); 26 | if (vCode != Proof.VerifyExistenceError.None) return VerifyMembershipError.ProofVerify; 27 | 28 | return VerifyMembershipError.None; 29 | } 30 | 31 | enum VerifyNonMembershipError { 32 | None, 33 | NonExistenceProofIsNil, 34 | ProofVerify 35 | } 36 | function verifyNonMembership(ProofSpec.Data memory spec, bytes memory commitmentRoot, CommitmentProof.Data memory proof, bytes memory key) internal pure returns(VerifyNonMembershipError) { 37 | CommitmentProof.Data memory decoProof = Compress.decompress(proof); 38 | NonExistenceProof.Data memory nonProof = getNonExistProofForKey(decoProof, key); 39 | //require(NonExistenceProof.isNil(nonProof) == false); // dev: getNonExistProofForKey not available 40 | if (NonExistenceProof.isNil(nonProof)) return VerifyNonMembershipError.NonExistenceProofIsNil; 41 | Proof.VerifyNonExistenceError vCode = Proof.verify(nonProof, spec, commitmentRoot, key); 42 | if (vCode != Proof.VerifyNonExistenceError.None) return VerifyNonMembershipError.ProofVerify; 43 | 44 | return VerifyNonMembershipError.None; 45 | } 46 | /* -- temporarily disabled as they are not covered by unit tests 47 | struct BatchItem { 48 | bytes key; 49 | bytes value; 50 | } 51 | function batchVerifyMembership(ProofSpec.Data memory spec, bytes memory commitmentRoot, CommitmentProof.Data memory proof, BatchItem[] memory items ) internal pure { 52 | CommitmentProof.Data memory decoProof = Compress.decompress(proof); 53 | for (uint i = 0; i < items.length; i++) { 54 | verifyMembership(spec, commitmentRoot, decoProof, items[i].key, items[i].value); 55 | } 56 | } 57 | 58 | function batchVerifyNonMembership(ProofSpec.Data memory spec, bytes memory commitmentRoot, CommitmentProof.Data memory proof, bytes[] memory keys ) internal pure { 59 | CommitmentProof.Data memory decoProof = Compress.decompress(proof); 60 | for (uint i = 0; i < keys.length; i++) { 61 | verifyNonMembership(spec, commitmentRoot, decoProof, keys[i]); 62 | } 63 | } 64 | */ 65 | 66 | // private 67 | function getExistProofForKey(CommitmentProof.Data memory proof, bytes memory key) private pure returns(ExistenceProof.Data memory) { 68 | if (ExistenceProof.isNil(proof.exist) == false){ 69 | if (BytesLib.equal(proof.exist.key, key) == true) { 70 | return proof.exist; 71 | } 72 | } else if(BatchProof.isNil(proof.batch) == false) { 73 | for (uint i = 0; i < proof.batch.entries.length; i++) { 74 | if (ExistenceProof.isNil(proof.batch.entries[i].exist) == false && 75 | BytesLib.equal(proof.batch.entries[i].exist.key, key)) { 76 | return proof.batch.entries[i].exist; 77 | } 78 | } 79 | } 80 | return ExistenceProof.nil(); 81 | } 82 | 83 | function getNonExistProofForKey(CommitmentProof.Data memory proof, bytes memory key) private pure returns(NonExistenceProof.Data memory) { 84 | if (NonExistenceProof.isNil(proof.nonexist) == false) { 85 | if (isLeft(proof.nonexist.left, key) && isRight(proof.nonexist.right, key)) { 86 | return proof.nonexist; 87 | } 88 | } else if (BatchProof.isNil(proof.batch) == false) { 89 | for (uint i = 0; i < proof.batch.entries.length; i++) { 90 | if (NonExistenceProof.isNil(proof.batch.entries[i].nonexist) == false && 91 | isLeft(proof.batch.entries[i].nonexist.left, key) && 92 | isRight(proof.batch.entries[i].nonexist.right, key)) { 93 | return proof.batch.entries[i].nonexist; 94 | } 95 | } 96 | } 97 | return NonExistenceProof.nil(); 98 | } 99 | 100 | function isLeft(ExistenceProof.Data memory left, bytes memory key) private pure returns(bool) { 101 | // ExistenceProof.isNil does not work 102 | return ExistenceProof._empty(left) || Ops.compare(left.key, key) < 0; 103 | } 104 | 105 | function isRight(ExistenceProof.Data memory right, bytes memory key) private pure returns(bool) { 106 | // ExistenceProof.isNil does not work 107 | return ExistenceProof._empty(right) || Ops.compare(right.key, key) > 0; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /contracts/ics23/ics23Compress.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/ChorusOne/ics23/tree/giulio/solidity 3 | // SPDX-License-Identifier: Apache-2.0 4 | pragma solidity ^0.8.2; 5 | 6 | import {InnerOp, ExistenceProof, NonExistenceProof, CommitmentProof, CompressedBatchEntry, CompressedBatchProof, CompressedExistenceProof, BatchEntry, BatchProof} from "../proto/proofs.sol"; 7 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 8 | 9 | library Compress { 10 | function decompress(CommitmentProof.Data memory proof) internal pure returns(CommitmentProof.Data memory) { 11 | //CompressedBatchProof.isNil() does not work 12 | if (CompressedBatchProof._empty(proof.compressed) == true){ 13 | return proof; 14 | } 15 | return CommitmentProof.Data({ 16 | exist: ExistenceProof.nil(), 17 | nonexist: NonExistenceProof.nil(), 18 | batch: BatchProof.Data({ 19 | entries: decompress(proof.compressed) 20 | }), 21 | compressed: CompressedBatchProof.nil() 22 | }); 23 | } 24 | 25 | // private 26 | function decompress(CompressedBatchProof.Data memory proof) private pure returns(BatchEntry.Data[] memory) { 27 | BatchEntry.Data[] memory entries = new BatchEntry.Data[](proof.entries.length); 28 | for(uint i = 0; i < proof.entries.length; i++) { 29 | entries[i] = decompressEntry(proof.entries[i], proof.lookup_inners); 30 | } 31 | return entries; 32 | } 33 | 34 | function decompressEntry(CompressedBatchEntry.Data memory entry, InnerOp.Data[] memory lookup) private pure returns(BatchEntry.Data memory) { 35 | //CompressedExistenceProof.isNil does not work 36 | if (CompressedExistenceProof._empty(entry.exist) == false) { 37 | return BatchEntry.Data({ 38 | exist: decompressExist(entry.exist, lookup), 39 | nonexist: NonExistenceProof.nil() 40 | }); 41 | } 42 | return BatchEntry.Data({ 43 | exist: ExistenceProof.nil(), 44 | nonexist: NonExistenceProof.Data({ 45 | key: entry.nonexist.key, 46 | left: decompressExist(entry.nonexist.left, lookup), 47 | right: decompressExist(entry.nonexist.right, lookup) 48 | }) 49 | }); 50 | } 51 | 52 | function decompressExist(CompressedExistenceProof.Data memory proof, InnerOp.Data[] memory lookup) private pure returns(ExistenceProof.Data memory) { 53 | if (CompressedExistenceProof._empty(proof)) { 54 | return ExistenceProof.nil(); 55 | } 56 | ExistenceProof.Data memory decoProof = ExistenceProof.Data({ 57 | key: proof.key, 58 | value: proof.value, 59 | leaf: proof.leaf, 60 | path : new InnerOp.Data[](proof.path.length) 61 | }); 62 | for (uint i = 0; i < proof.path.length; i++) { 63 | require(proof.path[i] >= 0); // dev: proof.path < 0 64 | uint step = SafeCast.toUint256(proof.path[i]); 65 | require(step < lookup.length); // dev: step >= lookup.length 66 | decoProof.path[i] = lookup[step]; 67 | } 68 | return decoProof; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/ics23/ics23Ops.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | // Source: https://github.com/ChorusOne/ics23/tree/giulio/solidity 3 | // SPDX-License-Identifier: Apache-2.0 4 | pragma solidity ^0.8.2; 5 | 6 | import {LeafOp, InnerOp, PROOFS_PROTO_GLOBAL_ENUMS, ProofSpec} from "../proto/proofs.sol"; 7 | import {ProtoBufRuntime} from "../proto/ProtoBufRuntime.sol"; 8 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 9 | import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; 10 | import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; 11 | 12 | library Ops { 13 | bytes constant empty = new bytes(0); 14 | 15 | enum ApplyLeafOpError { 16 | None, 17 | KeyLength, 18 | ValueLength, 19 | DoHash, 20 | PrepareLeafData 21 | } 22 | // LeafOp operations 23 | function applyOp(LeafOp.Data memory leafOp, bytes memory key, bytes memory value) internal pure returns(bytes memory, ApplyLeafOpError) { 24 | //require(key.length > 0); // dev: Leaf op needs key 25 | if (key.length == 0) return (empty, ApplyLeafOpError.KeyLength); 26 | //require(value.length > 0); // dev: Leaf op needs value 27 | if (value.length == 0) return (empty, ApplyLeafOpError.ValueLength); 28 | (bytes memory pKey, PrepareLeafDataError pCode1) = prepareLeafData(leafOp.prehash_key, leafOp.length, key); 29 | if (pCode1 != PrepareLeafDataError.None) return (empty, ApplyLeafOpError.PrepareLeafData); 30 | (bytes memory pValue, PrepareLeafDataError pCode2) = prepareLeafData(leafOp.prehash_value, leafOp.length, value); 31 | if (pCode2 != PrepareLeafDataError.None) return (empty, ApplyLeafOpError.PrepareLeafData); 32 | bytes memory data = abi.encodePacked(leafOp.prefix, pKey, pValue); 33 | (bytes memory hashed, DoHashError hCode) = doHash(leafOp.hash, data); 34 | if (hCode != DoHashError.None) return (empty, ApplyLeafOpError.DoHash); 35 | return(hashed, ApplyLeafOpError.None); 36 | } 37 | 38 | enum PrepareLeafDataError { 39 | None, 40 | DoHash, 41 | DoLengthOp 42 | } 43 | // preapare leaf data for encoding 44 | function prepareLeafData(PROOFS_PROTO_GLOBAL_ENUMS.HashOp hashOp, PROOFS_PROTO_GLOBAL_ENUMS.LengthOp lenOp, bytes memory data) internal pure returns(bytes memory, PrepareLeafDataError) { 45 | (bytes memory hased, DoHashError hCode) = doHashOrNoop(hashOp, data); 46 | if (hCode != DoHashError.None)return (empty, PrepareLeafDataError.DoHash); 47 | (bytes memory res, DoLengthOpError lCode) = doLengthOp(lenOp, hased); 48 | if (lCode != DoLengthOpError.None) return (empty, PrepareLeafDataError.DoLengthOp); 49 | 50 | return (res, PrepareLeafDataError.None); 51 | } 52 | 53 | enum CheckAgainstSpecError{ 54 | None, 55 | Hash, 56 | PreHashKey, 57 | PreHashValue, 58 | Length, 59 | MinPrefixLength, 60 | HasPrefix, 61 | MaxPrefixLength 62 | } 63 | function checkAgainstSpec(LeafOp.Data memory leafOp, ProofSpec.Data memory spec) internal pure returns(CheckAgainstSpecError) { 64 | //require (leafOp.hash == spec.leaf_spec.hash); // dev: checkAgainstSpec for LeafOp - Unexpected HashOp 65 | if (leafOp.hash != spec.leaf_spec.hash) return CheckAgainstSpecError.Hash; 66 | //require(leafOp.prehash_key == spec.leaf_spec.prehash_key); // dev: checkAgainstSpec for LeafOp - Unexpected PrehashKey 67 | if (leafOp.prehash_key != spec.leaf_spec.prehash_key) return CheckAgainstSpecError.PreHashKey; 68 | //require(leafOp.prehash_value == spec.leaf_spec.prehash_value); // dev: checkAgainstSpec for LeafOp - Unexpected PrehashValue"); 69 | if (leafOp.prehash_value != spec.leaf_spec.prehash_value) return CheckAgainstSpecError.PreHashValue; 70 | //require(leafOp.length == spec.leaf_spec.length); // dev: checkAgainstSpec for LeafOp - Unexpected lengthOp 71 | if (leafOp.length != spec.leaf_spec.length) return CheckAgainstSpecError.Length; 72 | bool hasprefix = hasPrefix(leafOp.prefix, spec.leaf_spec.prefix); 73 | //require(hasprefix); // dev: checkAgainstSpec for LeafOp - Leaf Prefix doesn't start with 74 | if (hasprefix == false) return CheckAgainstSpecError.HasPrefix; 75 | 76 | return CheckAgainstSpecError.None; 77 | } 78 | 79 | enum ApplyInnerOpError { 80 | None, 81 | ChildLength, 82 | DoHash 83 | } 84 | // InnerOp operations 85 | function applyOp(InnerOp.Data memory innerOp, bytes memory child ) internal pure returns(bytes memory, ApplyInnerOpError) { 86 | //require(child.length > 0); // dev: Inner op needs child value 87 | if (child.length == 0) return (empty, ApplyInnerOpError.ChildLength); 88 | bytes memory preImage = abi.encodePacked(innerOp.prefix, child, innerOp.suffix); 89 | (bytes memory hashed, DoHashError code) = doHash(innerOp.hash, preImage); 90 | if (code != DoHashError.None) return (empty, ApplyInnerOpError.DoHash); 91 | 92 | return (hashed, ApplyInnerOpError.None); 93 | } 94 | 95 | function checkAgainstSpec(InnerOp.Data memory innerOp, ProofSpec.Data memory spec) internal pure returns(CheckAgainstSpecError) { 96 | //require(innerOp.hash == spec.inner_spec.hash); // dev: checkAgainstSpec for InnerOp - Unexpected HashOp 97 | if (innerOp.hash != spec.inner_spec.hash) return CheckAgainstSpecError.Hash; 98 | uint256 minPrefixLength = SafeCast.toUint256(spec.inner_spec.min_prefix_length); 99 | //require(innerOp.prefix.length >= minPrefixLength); // dev: InnerOp prefix too short; 100 | if (innerOp.prefix.length < minPrefixLength) return CheckAgainstSpecError.MinPrefixLength; 101 | bytes memory leafPrefix = spec.leaf_spec.prefix; 102 | bool hasprefix = hasPrefix(innerOp.prefix, leafPrefix); 103 | //require(hasprefix == false); // dev: Inner Prefix starts with wrong value 104 | if (hasprefix) return CheckAgainstSpecError.HasPrefix; 105 | uint256 childSize = SafeCast.toUint256(spec.inner_spec.child_size); 106 | uint256 maxLeftChildBytes = (spec.inner_spec.child_order.length - 1) * childSize; 107 | uint256 maxPrefixLength = SafeCast.toUint256(spec.inner_spec.max_prefix_length); 108 | //require(innerOp.prefix.length <= maxPrefixLength + maxLeftChildBytes); // dev: InnerOp prefix too long 109 | if (innerOp.prefix.length > maxPrefixLength + maxLeftChildBytes) return CheckAgainstSpecError.MaxPrefixLength; 110 | 111 | return CheckAgainstSpecError.None; 112 | } 113 | 114 | function doHashOrNoop(PROOFS_PROTO_GLOBAL_ENUMS.HashOp hashOp, bytes memory preImage) internal pure returns(bytes memory, DoHashError) { 115 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.NO_HASH) { 116 | return (preImage, DoHashError.None); 117 | } 118 | return doHash(hashOp, preImage); 119 | } 120 | 121 | enum DoHashError { 122 | None, 123 | Sha512, 124 | Sha512_256, 125 | Unsupported 126 | } 127 | function doHash(PROOFS_PROTO_GLOBAL_ENUMS.HashOp hashOp, bytes memory preImage) internal pure returns(bytes memory, DoHashError) { 128 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.SHA256) { 129 | return (abi.encodePacked(sha256(preImage)), DoHashError.None); 130 | } 131 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.KECCAK) { 132 | return (abi.encodePacked(keccak256(preImage)), DoHashError.None); 133 | } 134 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.RIPEMD160) { 135 | return (abi.encodePacked(ripemd160(preImage)), DoHashError.None); 136 | } 137 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.BITCOIN) { 138 | bytes memory tmp = abi.encodePacked(sha256(preImage)); 139 | return (abi.encodePacked(ripemd160(tmp)), DoHashError.None); 140 | } 141 | //require(hashOp != PROOFS_PROTO_GLOBAL_ENUMS.HashOp.Sha512); // dev: SHA512 not supported 142 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.SHA512) { 143 | return (empty, DoHashError.Sha512); 144 | } 145 | //require(hashOp != PROOFS_PROTO_GLOBAL_ENUMS.HashOp.Sha512_256); // dev: SHA512_256 not supported 146 | if (hashOp == PROOFS_PROTO_GLOBAL_ENUMS.HashOp.SHA512_256) { 147 | return (empty, DoHashError.Sha512_256); 148 | } 149 | //revert(); // dev: Unsupported hashOp 150 | return (empty, DoHashError.Unsupported); 151 | } 152 | 153 | function compare(bytes memory a, bytes memory b) internal pure returns(int) { 154 | uint256 minLen = Math.min(a.length, b.length); 155 | for (uint i = 0; i < minLen; i++) { 156 | if (uint8(a[i]) < uint8(b[i])) { 157 | return -1; 158 | } else if (uint8(a[i]) > uint8(b[i])) { 159 | return 1; 160 | } 161 | } 162 | if (a.length > minLen) { 163 | return 1; 164 | } 165 | if (b.length > minLen) { 166 | return -1; 167 | } 168 | return 0; 169 | } 170 | 171 | // private 172 | enum DoLengthOpError { 173 | None, 174 | Require32DataLength, 175 | Require64DataLength, 176 | Unsupported 177 | } 178 | function doLengthOp(PROOFS_PROTO_GLOBAL_ENUMS.LengthOp lenOp, bytes memory data) private pure returns(bytes memory, DoLengthOpError) { 179 | if (lenOp == PROOFS_PROTO_GLOBAL_ENUMS.LengthOp.NO_PREFIX) { 180 | return (data, DoLengthOpError.None); 181 | } 182 | if (lenOp == PROOFS_PROTO_GLOBAL_ENUMS.LengthOp.VAR_PROTO) { 183 | uint256 sz = ProtoBufRuntime._sz_varint(data.length); 184 | bytes memory encoded = new bytes(sz); 185 | ProtoBufRuntime._encode_varint(data.length, 32, encoded); 186 | return (abi.encodePacked(encoded, data), DoLengthOpError.None); 187 | } 188 | if (lenOp == PROOFS_PROTO_GLOBAL_ENUMS.LengthOp.REQUIRE_32_BYTES) { 189 | //require(data.length == 32); // dev: data.length != 32 190 | if (data.length != 32) return (empty, DoLengthOpError.Require32DataLength); 191 | 192 | return (data, DoLengthOpError.None); 193 | } 194 | if (lenOp == PROOFS_PROTO_GLOBAL_ENUMS.LengthOp.REQUIRE_64_BYTES) { 195 | //require(data.length == 64); // dev: data.length != 64" 196 | if (data.length != 64) return (empty, DoLengthOpError.Require64DataLength); 197 | 198 | return (data, DoLengthOpError.None); 199 | } 200 | if (lenOp == PROOFS_PROTO_GLOBAL_ENUMS.LengthOp.FIXED32_LITTLE) { 201 | uint32 size = SafeCast.toUint32(data.length); 202 | // maybe some assembly here to make it faster 203 | bytes4 sizeB = bytes4(size); 204 | bytes memory littleE = new bytes(4); 205 | //unfolding for loop is cheaper 206 | littleE[0] = sizeB[3]; 207 | littleE[1] = sizeB[2]; 208 | littleE[2] = sizeB[1]; 209 | littleE[3] = sizeB[0]; 210 | return (abi.encodePacked(littleE, data), DoLengthOpError.None); 211 | } 212 | //revert(); // dev: Unsupported lenOp 213 | return (empty, DoLengthOpError.Unsupported); 214 | } 215 | 216 | function hasPrefix(bytes memory element, bytes memory prefix) private pure returns (bool) { 217 | if (prefix.length == 0) { 218 | return true; 219 | } 220 | if (prefix.length > element.length) { 221 | return false; 222 | } 223 | bytes memory slice = BytesLib.slice(element, 0, prefix.length); 224 | return BytesLib.equal(prefix, slice); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /contracts/mocks/Ed25519Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "../utils/crypto/Ed25519.sol"; 6 | 7 | contract Ed25519Mock { 8 | function verify( 9 | bytes memory message, 10 | bytes memory publicKey, 11 | bytes memory sig 12 | ) public view returns (bool) { 13 | return Ed25519.verify(message, publicKey, sig); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/MerkleTreeMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "../utils/crypto/MerkleTree.sol"; 6 | import {Validator, ValidatorSet, Fraction} from "../proto/TendermintLight.sol"; 7 | 8 | contract MerkleTreeMock { 9 | function merkleRootHash( 10 | bytes memory validators, 11 | uint256 start, 12 | uint256 total 13 | ) public pure returns (bytes32) { 14 | ValidatorSet.Data memory vs = ValidatorSet.decode(validators); 15 | 16 | require(vs.validators.length == total, "requested vs provided validator size differ"); 17 | if (total > 0) { 18 | require(vs.validators[0].pub_key.ed25519.length > 0, "expected ed25519 public key, got empty array"); 19 | } 20 | 21 | return MerkleTree.merkleRootHash(vs.validators, start, total); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/mocks/ProtoMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import {TmHeader} from "../proto/TendermintLight.sol"; 6 | import {GoogleProtobufAny as Any} from "../proto/GoogleProtobufAny.sol"; 7 | 8 | contract ProtoMock { 9 | 10 | function unmarshalHeader(bytes memory headerBytes, string memory chainID) public { 11 | Any.Data memory anyHeader = Any.decode(headerBytes); 12 | TmHeader.Data memory header = TmHeader.decode(anyHeader.value); 13 | 14 | // simple check to verify decoded header 15 | require( 16 | keccak256(abi.encodePacked(chainID)) == keccak256(abi.encodePacked(header.signed_header.header.chain_id)), 17 | "invalid chain_id" 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/mocks/Secp256k1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "../utils/crypto/Secp256k1.sol"; 6 | 7 | contract Secp256k1Mock { 8 | function serializePubkey(bytes memory pubkey, bool prefix) public view returns (bytes memory) { 9 | return Secp256k1.serializePubkey(pubkey, prefix); 10 | } 11 | 12 | function verify( 13 | bytes memory message, 14 | bytes memory publicKey, 15 | bytes memory sig 16 | ) public view returns (bool) { 17 | return Secp256k1.verify(message, publicKey, sig); 18 | } 19 | 20 | function recover( 21 | bytes memory message, 22 | bytes memory sig, 23 | uint8 v 24 | ) public pure returns (address) { 25 | (address recovered, ) = Secp256k1.tryRecover(sha256(message), sig, v); 26 | return recovered; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/mocks/TendermintMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "../proto/TendermintHelper.sol"; 6 | import {SignedHeader, ValidatorSet} from "../proto/TendermintLight.sol"; 7 | 8 | contract TendermintMock { 9 | function signedHeaderHash(bytes memory data) public pure returns (bytes32) { 10 | SignedHeader.Data memory sh = SignedHeader.decode(data); 11 | return TendermintHelper.hash(sh); 12 | } 13 | 14 | function validatorSetHash(bytes memory data) public pure returns (bytes32) { 15 | ValidatorSet.Data memory vs = ValidatorSet.decode(data); 16 | return TendermintHelper.hash(vs); 17 | } 18 | 19 | function totalVotingPower(bytes memory data) public pure returns (int64) { 20 | ValidatorSet.Data memory vs = ValidatorSet.decode(data); 21 | return TendermintHelper.getTotalVotingPower(vs); 22 | } 23 | 24 | function getByAddress(bytes memory data, bytes memory addr) public pure returns (uint256 index, bool found) { 25 | ValidatorSet.Data memory vs = ValidatorSet.decode(data); 26 | return TendermintHelper.getByAddress(vs, addr); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chorusone/tendermint", 3 | "description": "Tendermint library for Solidity", 4 | "version": "4.3.2", 5 | "files": [ 6 | "**/*.sol", 7 | "/build/contracts/*.json", 8 | "!/mocks/**/*" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ChorusOne/tendermint-sol.git" 13 | }, 14 | "keywords": [ 15 | "tendermint", 16 | "solidity" 17 | ], 18 | "author": "Mateusz Kaczanowski ", 19 | "license": "TBD", 20 | "bugs": { 21 | "url": "https://github.com/ChrousOne/tendermint-sol/issues" 22 | }, 23 | "homepage": "https://chorus.one" 24 | } 25 | -------------------------------------------------------------------------------- /contracts/proto/Encoder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "./ProtoBufRuntime.sol"; 6 | 7 | library Encoder { 8 | uint64 private constant _MAX_UINT64 = 0xFFFFFFFFFFFFFFFF; 9 | 10 | function cdcEncode(string memory item) internal pure returns (bytes memory) { 11 | uint256 estimatedSize = 1 + ProtoBufRuntime._sz_lendelim(bytes(item).length); 12 | bytes memory bs = new bytes(estimatedSize); 13 | 14 | uint256 offset = 32; 15 | uint256 pointer = 32; 16 | 17 | if (bytes(item).length > 0) { 18 | pointer += ProtoBufRuntime._encode_key(1, ProtoBufRuntime.WireType.LengthDelim, pointer, bs); 19 | pointer += ProtoBufRuntime._encode_string(item, pointer, bs); 20 | } 21 | 22 | uint256 sz = pointer - offset; 23 | assembly { 24 | mstore(bs, sz) 25 | } 26 | return bs; 27 | } 28 | 29 | function cdcEncode(bytes memory item) internal pure returns (bytes memory) { 30 | uint256 estimatedSize = 1 + ProtoBufRuntime._sz_lendelim(item.length); 31 | bytes memory bs = new bytes(estimatedSize); 32 | 33 | uint256 offset = 32; 34 | uint256 pointer = 32; 35 | 36 | if (item.length > 0) { 37 | pointer += ProtoBufRuntime._encode_key(1, ProtoBufRuntime.WireType.LengthDelim, pointer, bs); 38 | pointer += ProtoBufRuntime._encode_bytes(item, pointer, bs); 39 | } 40 | 41 | uint256 sz = pointer - offset; 42 | assembly { 43 | mstore(bs, sz) 44 | } 45 | return bs; 46 | } 47 | 48 | function cdcEncode(int64 item) internal pure returns (bytes memory) { 49 | uint256 estimatedSize = 1 + ProtoBufRuntime._sz_int64(item); 50 | bytes memory bs = new bytes(estimatedSize); 51 | 52 | uint256 offset = 32; 53 | uint256 pointer = 32; 54 | 55 | if (item != 0) { 56 | pointer += ProtoBufRuntime._encode_key(1, ProtoBufRuntime.WireType.Varint, pointer, bs); 57 | pointer += ProtoBufRuntime._encode_int64(item, pointer, bs); 58 | } 59 | 60 | uint256 sz = pointer - offset; 61 | assembly { 62 | mstore(bs, sz) 63 | } 64 | return bs; 65 | } 66 | 67 | // TODO: Can we make this cheaper? 68 | // https://docs.soliditylang.org/en/v0.6.5/types.html#allocating-memory-arrays 69 | function encodeDelim(bytes memory input) internal pure returns (bytes memory) { 70 | require(input.length < _MAX_UINT64, "Encoder: out of bounds (uint64)"); 71 | 72 | uint64 length = uint64(input.length); 73 | uint256 additionalEstimated = ProtoBufRuntime._sz_uint64(length); 74 | 75 | bytes memory delimitedPrefix = new bytes(additionalEstimated); 76 | uint256 delimitedPrefixLen = ProtoBufRuntime._encode_uint64(length, 32, delimitedPrefix); 77 | 78 | assembly { 79 | mstore(delimitedPrefix, delimitedPrefixLen) 80 | } 81 | 82 | // concatenate buffers 83 | return abi.encodePacked(delimitedPrefix, input); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/proto/GoogleProtobufAny.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.2; 3 | import "./ProtoBufRuntime.sol"; 4 | 5 | library GoogleProtobufAny { 6 | 7 | 8 | //struct definition 9 | struct Data { 10 | string type_url; 11 | bytes value; 12 | } 13 | 14 | // Decoder section 15 | 16 | /** 17 | * @dev The main decoder for memory 18 | * @param bs The bytes array to be decoded 19 | * @return The decoded struct 20 | */ 21 | function decode(bytes memory bs) internal pure returns (Data memory) { 22 | (Data memory x, ) = _decode(32, bs, bs.length); 23 | return x; 24 | } 25 | 26 | /** 27 | * @dev The main decoder for storage 28 | * @param self The in-storage struct 29 | * @param bs The bytes array to be decoded 30 | */ 31 | function decode(Data storage self, bytes memory bs) internal { 32 | (Data memory x, ) = _decode(32, bs, bs.length); 33 | store(x, self); 34 | } 35 | // inner decoder 36 | 37 | /** 38 | * @dev The decoder for internal usage 39 | * @param p The offset of bytes array to start decode 40 | * @param bs The bytes array to be decoded 41 | * @param sz The number of bytes expected 42 | * @return The decoded struct 43 | * @return The number of bytes decoded 44 | */ 45 | function _decode(uint256 p, bytes memory bs, uint256 sz) 46 | internal 47 | pure 48 | returns (Data memory, uint) 49 | { 50 | Data memory r; 51 | uint[3] memory counters; 52 | uint256 fieldId; 53 | ProtoBufRuntime.WireType wireType; 54 | uint256 bytesRead; 55 | uint256 offset = p; 56 | uint256 pointer = p; 57 | while (pointer < offset + sz) { 58 | (fieldId, wireType, bytesRead) = ProtoBufRuntime._decode_key(pointer, bs); 59 | pointer += bytesRead; 60 | if (fieldId == 1) { 61 | pointer += _read_type_url(pointer, bs, r, counters); 62 | } 63 | else if (fieldId == 2) { 64 | pointer += _read_value(pointer, bs, r, counters); 65 | } 66 | 67 | else { 68 | if (wireType == ProtoBufRuntime.WireType.Fixed64) { 69 | uint256 size; 70 | (, size) = ProtoBufRuntime._decode_fixed64(pointer, bs); 71 | pointer += size; 72 | } 73 | if (wireType == ProtoBufRuntime.WireType.Fixed32) { 74 | uint256 size; 75 | (, size) = ProtoBufRuntime._decode_fixed32(pointer, bs); 76 | pointer += size; 77 | } 78 | if (wireType == ProtoBufRuntime.WireType.Varint) { 79 | uint256 size; 80 | (, size) = ProtoBufRuntime._decode_varint(pointer, bs); 81 | pointer += size; 82 | } 83 | if (wireType == ProtoBufRuntime.WireType.LengthDelim) { 84 | uint256 size; 85 | (, size) = ProtoBufRuntime._decode_lendelim(pointer, bs); 86 | pointer += size; 87 | } 88 | } 89 | 90 | } 91 | return (r, sz); 92 | } 93 | 94 | // field readers 95 | 96 | /** 97 | * @dev The decoder for reading a field 98 | * @param p The offset of bytes array to start decode 99 | * @param bs The bytes array to be decoded 100 | * @param r The in-memory struct 101 | * @param counters The counters for repeated fields 102 | * @return The number of bytes decoded 103 | */ 104 | function _read_type_url( 105 | uint256 p, 106 | bytes memory bs, 107 | Data memory r, 108 | uint[3] memory counters 109 | ) internal pure returns (uint) { 110 | // 111 | // if `r` is NULL, then only counting the number of fields. 112 | // 113 | (string memory x, uint256 sz) = ProtoBufRuntime._decode_string(p, bs); 114 | if (isNil(r)) { 115 | counters[1] += 1; 116 | } else { 117 | r.type_url = x; 118 | if (counters[1] > 0) counters[1] -= 1; 119 | } 120 | return sz; 121 | } 122 | 123 | /** 124 | * @dev The decoder for reading a field 125 | * @param p The offset of bytes array to start decode 126 | * @param bs The bytes array to be decoded 127 | * @param r The in-memory struct 128 | * @param counters The counters for repeated fields 129 | * @return The number of bytes decoded 130 | */ 131 | function _read_value( 132 | uint256 p, 133 | bytes memory bs, 134 | Data memory r, 135 | uint[3] memory counters 136 | ) internal pure returns (uint) { 137 | // 138 | // if `r` is NULL, then only counting the number of fields. 139 | // 140 | (bytes memory x, uint256 sz) = ProtoBufRuntime._decode_bytes(p, bs); 141 | if (isNil(r)) { 142 | counters[2] += 1; 143 | } else { 144 | r.value = x; 145 | if (counters[2] > 0) counters[2] -= 1; 146 | } 147 | return sz; 148 | } 149 | 150 | 151 | // Encoder section 152 | 153 | /** 154 | * @dev The main encoder for memory 155 | * @param r The struct to be encoded 156 | * @return The encoded byte array 157 | */ 158 | function encode(Data memory r) internal pure returns (bytes memory) { 159 | bytes memory bs = new bytes(_estimate(r)); 160 | uint256 sz = _encode(r, 32, bs); 161 | assembly { 162 | mstore(bs, sz) 163 | } 164 | return bs; 165 | } 166 | // inner encoder 167 | 168 | /** 169 | * @dev The encoder for internal usage 170 | * @param r The struct to be encoded 171 | * @param p The offset of bytes array to start decode 172 | * @param bs The bytes array to be decoded 173 | * @return The number of bytes encoded 174 | */ 175 | function _encode(Data memory r, uint256 p, bytes memory bs) 176 | internal 177 | pure 178 | returns (uint) 179 | { 180 | uint256 offset = p; 181 | uint256 pointer = p; 182 | 183 | pointer += ProtoBufRuntime._encode_key( 184 | 1, 185 | ProtoBufRuntime.WireType.LengthDelim, 186 | pointer, 187 | bs 188 | ); 189 | pointer += ProtoBufRuntime._encode_string(r.type_url, pointer, bs); 190 | pointer += ProtoBufRuntime._encode_key( 191 | 2, 192 | ProtoBufRuntime.WireType.LengthDelim, 193 | pointer, 194 | bs 195 | ); 196 | pointer += ProtoBufRuntime._encode_bytes(r.value, pointer, bs); 197 | return pointer - offset; 198 | } 199 | // nested encoder 200 | 201 | /** 202 | * @dev The encoder for inner struct 203 | * @param r The struct to be encoded 204 | * @param p The offset of bytes array to start decode 205 | * @param bs The bytes array to be decoded 206 | * @return The number of bytes encoded 207 | */ 208 | function _encode_nested(Data memory r, uint256 p, bytes memory bs) 209 | internal 210 | pure 211 | returns (uint) 212 | { 213 | // 214 | // First encoded `r` into a temporary array, and encode the actual size used. 215 | // Then copy the temporary array into `bs`. 216 | // 217 | uint256 offset = p; 218 | uint256 pointer = p; 219 | bytes memory tmp = new bytes(_estimate(r)); 220 | uint256 tmpAddr = ProtoBufRuntime.getMemoryAddress(tmp); 221 | uint256 bsAddr = ProtoBufRuntime.getMemoryAddress(bs); 222 | uint256 size = _encode(r, 32, tmp); 223 | pointer += ProtoBufRuntime._encode_varint(size, pointer, bs); 224 | ProtoBufRuntime.copyBytes(tmpAddr + 32, bsAddr + pointer, size); 225 | pointer += size; 226 | delete tmp; 227 | return pointer - offset; 228 | } 229 | // estimator 230 | 231 | /** 232 | * @dev The estimator for a struct 233 | * @param r The struct to be encoded 234 | * @return The number of bytes encoded in estimation 235 | */ 236 | function _estimate( 237 | Data memory r 238 | ) internal pure returns (uint) { 239 | uint256 e; 240 | e += 1 + ProtoBufRuntime._sz_lendelim(bytes(r.type_url).length); 241 | e += 1 + ProtoBufRuntime._sz_lendelim(r.value.length); 242 | return e; 243 | } 244 | 245 | //store function 246 | /** 247 | * @dev Store in-memory struct to storage 248 | * @param input The in-memory struct 249 | * @param output The in-storage struct 250 | */ 251 | function store(Data memory input, Data storage output) internal { 252 | output.type_url = input.type_url; 253 | output.value = input.value; 254 | 255 | } 256 | 257 | 258 | 259 | //utility functions 260 | /** 261 | * @dev Return an empty struct 262 | * @return r The empty struct 263 | */ 264 | function nil() internal pure returns (Data memory r) { 265 | assembly { 266 | r := 0 267 | } 268 | } 269 | 270 | /** 271 | * @dev Test whether a struct is empty 272 | * @param x The struct to be tested 273 | * @return r True if it is empty 274 | */ 275 | function isNil(Data memory x) internal pure returns (bool r) { 276 | assembly { 277 | r := iszero(x) 278 | } 279 | } 280 | } 281 | //library Any 282 | -------------------------------------------------------------------------------- /contracts/proto/TendermintHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import {TENDERMINTLIGHT_PROTO_GLOBAL_ENUMS, Validator, SimpleValidator, BlockID, Vote, CanonicalBlockID, CanonicalPartSetHeader, CanonicalVote, TmHeader, ConsensusState, MerkleRoot, Commit, CommitSig, SignedHeader, ValidatorSet, Duration, Timestamp, Consensus} from "./TendermintLight.sol"; 6 | import "./Encoder.sol"; 7 | import "../utils/crypto/MerkleTree.sol"; 8 | import "@openzeppelin/contracts/utils/math/SafeCast.sol"; 9 | 10 | library TendermintHelper { 11 | function toSimpleValidator(Validator.Data memory val) internal pure returns (SimpleValidator.Data memory) { 12 | return SimpleValidator.Data({pub_key: val.pub_key, voting_power: val.voting_power}); 13 | } 14 | 15 | function toCanonicalBlockID(BlockID.Data memory blockID) internal pure returns (CanonicalBlockID.Data memory) { 16 | return 17 | CanonicalBlockID.Data({ 18 | hash: blockID.hash, 19 | part_set_header: CanonicalPartSetHeader.Data({ 20 | total: blockID.part_set_header.total, 21 | hash: blockID.part_set_header.hash 22 | }) 23 | }); 24 | } 25 | 26 | function toCanonicalVote(Vote.Data memory vote, string memory chainID) 27 | internal 28 | pure 29 | returns (CanonicalVote.Data memory) 30 | { 31 | return 32 | CanonicalVote.Data({ 33 | Type: vote.Type, 34 | height: vote.height, 35 | round: int64(vote.round), 36 | block_id: toCanonicalBlockID(vote.block_id), 37 | timestamp: vote.timestamp, 38 | chain_id: chainID 39 | }); 40 | } 41 | 42 | function toConsensusState(TmHeader.Data memory tmHeader) internal pure returns (ConsensusState.Data memory) { 43 | return 44 | ConsensusState.Data({ 45 | timestamp: tmHeader.signed_header.header.time, 46 | root: MerkleRoot.Data({hash: tmHeader.signed_header.header.app_hash}), 47 | next_validators_hash: tmHeader.signed_header.header.next_validators_hash 48 | }); 49 | } 50 | 51 | function toVote(Commit.Data memory commit, uint256 valIdx) internal pure returns (Vote.Data memory) { 52 | CommitSig.Data memory commitSig = commit.signatures[valIdx]; 53 | 54 | return 55 | Vote.Data({ 56 | Type: TENDERMINTLIGHT_PROTO_GLOBAL_ENUMS.SignedMsgType.SIGNED_MSG_TYPE_PRECOMMIT, 57 | height: commit.height, 58 | round: commit.round, 59 | block_id: commit.block_id, 60 | timestamp: commitSig.timestamp, 61 | validator_address: commitSig.validator_address, 62 | validator_index: SafeCast.toInt32(int256(valIdx)), 63 | signature: commitSig.signature 64 | }); 65 | } 66 | 67 | function isEqual(BlockID.Data memory b1, BlockID.Data memory b2) internal pure returns (bool) { 68 | if (keccak256(abi.encodePacked(b1.hash)) != keccak256(abi.encodePacked(b2.hash))) { 69 | return false; 70 | } 71 | 72 | if (b1.part_set_header.total != b2.part_set_header.total) { 73 | return false; 74 | } 75 | 76 | if ( 77 | keccak256(abi.encodePacked(b1.part_set_header.hash)) != keccak256(abi.encodePacked(b2.part_set_header.hash)) 78 | ) { 79 | return false; 80 | } 81 | 82 | return true; 83 | } 84 | 85 | function isEqual(ConsensusState.Data memory cs1, ConsensusState.Data memory cs2) internal pure returns (bool) { 86 | return 87 | keccak256(abi.encodePacked(ConsensusState.encode(cs1))) == 88 | keccak256(abi.encodePacked(ConsensusState.encode(cs2))); 89 | } 90 | 91 | function isExpired( 92 | SignedHeader.Data memory header, 93 | Duration.Data memory trustingPeriod, 94 | Duration.Data memory currentTime 95 | ) internal pure returns (bool) { 96 | Timestamp.Data memory expirationTime = Timestamp.Data({ 97 | Seconds: header.header.time.Seconds + int64(trustingPeriod.Seconds), 98 | nanos: header.header.time.nanos 99 | }); 100 | 101 | return gt(Timestamp.Data({Seconds: int64(currentTime.Seconds), nanos: 0}), expirationTime); 102 | } 103 | 104 | function gt(Timestamp.Data memory t1, Timestamp.Data memory t2) internal pure returns (bool) { 105 | if (t1.Seconds > t2.Seconds) { 106 | return true; 107 | } 108 | 109 | if (t1.Seconds == t2.Seconds && t1.nanos > t2.nanos) { 110 | return true; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | function hash(SignedHeader.Data memory h) internal pure returns (bytes32) { 117 | require(h.header.validators_hash.length > 0, "Tendermint: hash can't be empty"); 118 | 119 | bytes memory hbz = Consensus.encode(h.header.version); 120 | bytes memory pbt = Timestamp.encode(h.header.time); 121 | bytes memory bzbi = BlockID.encode(h.header.last_block_id); 122 | 123 | bytes[14] memory all = [ 124 | hbz, 125 | Encoder.cdcEncode(h.header.chain_id), 126 | Encoder.cdcEncode(h.header.height), 127 | pbt, 128 | bzbi, 129 | Encoder.cdcEncode(h.header.last_commit_hash), 130 | Encoder.cdcEncode(h.header.data_hash), 131 | Encoder.cdcEncode(h.header.validators_hash), 132 | Encoder.cdcEncode(h.header.next_validators_hash), 133 | Encoder.cdcEncode(h.header.consensus_hash), 134 | Encoder.cdcEncode(h.header.app_hash), 135 | Encoder.cdcEncode(h.header.last_results_hash), 136 | Encoder.cdcEncode(h.header.evidence_hash), 137 | Encoder.cdcEncode(h.header.proposer_address) 138 | ]; 139 | 140 | return MerkleTree.merkleRootHash(all, 0, all.length); 141 | } 142 | 143 | function hash(ValidatorSet.Data memory vs) internal pure returns (bytes32) { 144 | return MerkleTree.merkleRootHash(vs.validators, 0, vs.validators.length); 145 | } 146 | 147 | function getByAddress(ValidatorSet.Data memory vals, bytes memory addr) 148 | internal 149 | pure 150 | returns (uint256 index, bool found) 151 | { 152 | bytes32 addrHash = keccak256(abi.encodePacked(addr)); 153 | for (uint256 idx; idx < vals.validators.length; idx++) { 154 | if (keccak256(abi.encodePacked(vals.validators[idx].Address)) == addrHash) { 155 | return (idx, true); 156 | } 157 | } 158 | 159 | return (0, false); 160 | } 161 | 162 | function getTotalVotingPower(ValidatorSet.Data memory vals) internal pure returns (int64) { 163 | if (vals.total_voting_power == 0) { 164 | uint256 sum = 0; 165 | uint256 maxInt64 = 1 << (63 - 1); 166 | uint256 maxTotalVotingPower = maxInt64 / 8; 167 | 168 | for (uint256 i = 0; i < vals.validators.length; i++) { 169 | sum += (SafeCast.toUint256(int256(vals.validators[i].voting_power))); 170 | require(sum <= maxTotalVotingPower, "total voting power should be guarded to not exceed"); 171 | } 172 | 173 | vals.total_voting_power = SafeCast.toInt64(int256(sum)); 174 | } 175 | 176 | return vals.total_voting_power; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /contracts/utils/Bytes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | pragma solidity ^0.8.2; 3 | 4 | library Bytes { 5 | function toBytes32(bytes memory bz) internal pure returns (bytes32 ret) { 6 | require(bz.length == 32, "Bytes: toBytes32 invalid size"); 7 | assembly { 8 | ret := mload(add(bz, 32)) 9 | } 10 | } 11 | 12 | function toBytes(bytes32 data) public pure returns (bytes memory) { 13 | return abi.encodePacked(data); 14 | } 15 | 16 | function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64 ret) { 17 | require(_bytes.length >= _start + 8, "Bytes: toUint64 out of bounds"); 18 | assembly { 19 | ret := mload(add(add(_bytes, 0x8), _start)) 20 | } 21 | } 22 | 23 | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { 24 | require(_bytes.length >= _start + 32, "Bytes: toUint256 out of bounds"); 25 | uint256 tempUint; 26 | 27 | assembly { 28 | tempUint := mload(add(add(_bytes, 0x20), _start)) 29 | } 30 | 31 | return tempUint; 32 | } 33 | 34 | function toAddress(bytes memory _bytes) internal pure returns (address addr) { 35 | // convert last 20 bytes of keccak hash (bytes32) to address 36 | bytes32 hash = keccak256(_bytes); 37 | assembly { 38 | mstore(0, hash) 39 | addr := mload(0) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/utils/crypto/Ed25519.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | library Ed25519 { 6 | 7 | uint private constant _CELO_ED25519_PRECOMPILE_ADDR = 0xf3; 8 | 9 | /** 10 | * @dev verifies the ed25519 signature against the public key and message 11 | * This method works only with Celo Blockchain, by calling the ed25519 precompile (currently unavailable in vanilla EVM) 12 | * 13 | * See: tendermint/crypto/secp256k1/secp256k1_nocgo.go (Sign, Verify methods) 14 | */ 15 | function verify(bytes memory message, bytes memory publicKey, bytes memory signature) internal view returns (bool) { 16 | require(signature.length == 64, "Ed25519: siganture length != 64"); 17 | require(publicKey.length == 32, "Ed25519: pubkey length != 32"); 18 | 19 | bytes memory all = abi.encodePacked(publicKey, signature, message); 20 | bytes32 result = 0x0000000000000000000000000000000000000000000000000000000000000001; 21 | 22 | assembly { 23 | let success := staticcall(gas(), _CELO_ED25519_PRECOMPILE_ADDR, add(all, 0x20), mload(all), result, 0x20) 24 | 25 | switch success 26 | case 0 { 27 | revert(0, "ed25519 precompile failed") 28 | } default { 29 | result := mload(result) 30 | } 31 | } 32 | 33 | // result > 0 is an error 34 | return (bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) == result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/utils/crypto/MerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "../../proto/TendermintHelper.sol"; 6 | import { SimpleValidator, Validator } from "../../proto/TendermintLight.sol"; 7 | 8 | library MerkleTree { 9 | 10 | /** 11 | * @dev returns empty hash 12 | */ 13 | function emptyHash() internal pure returns (bytes32) { 14 | return sha256(abi.encode()); 15 | } 16 | 17 | /** 18 | * @dev returns tmhash(0x00 || leaf) 19 | * 20 | */ 21 | function leafHash(bytes memory leaf) internal pure returns (bytes32) { 22 | uint8 leafPrefix = 0x00; 23 | return sha256(abi.encodePacked(leafPrefix, leaf)); 24 | } 25 | 26 | /** 27 | * @dev returns tmhash(0x01 || left || right) 28 | */ 29 | function innerHash(bytes32 leaf, bytes32 right) internal pure returns (bytes32) { 30 | uint8 innerPrefix = 0x01; 31 | return sha256(abi.encodePacked(innerPrefix, leaf, right)); 32 | } 33 | 34 | /** 35 | * @dev returns the largest power of 2 less than length 36 | * 37 | * TODO: This function can be optimized with bit shifting approach: 38 | * https://www.baeldung.com/java-largest-power-of-2-less-than-number 39 | */ 40 | function getSplitPoint(uint256 input) internal pure returns (uint) { 41 | require(input > 1, "MerkleTree: invalid input"); 42 | 43 | uint result = 1; 44 | for (uint i = input - 1; i > 1; i--) { 45 | if ((i & (i - 1)) == 0) { 46 | result = i; 47 | break; 48 | } 49 | } 50 | return result; 51 | } 52 | 53 | /** 54 | * @dev computes a Merkle tree where the leaves are validators, in the provided order 55 | * Follows RFC-6962 56 | */ 57 | function merkleRootHash(Validator.Data[] memory validators, uint start, uint total) internal pure returns (bytes32) { 58 | if (total == 0) { 59 | return emptyHash(); 60 | } else if (total == 1) { 61 | bytes memory encodedValidator = SimpleValidator.encode(TendermintHelper.toSimpleValidator(validators[start])); 62 | return leafHash(encodedValidator); 63 | } else { 64 | uint k = getSplitPoint(total); 65 | bytes32 left = merkleRootHash(validators, start, k); // validators[:k] 66 | bytes32 right = merkleRootHash(validators, start+k, total-k); // validators[k:] 67 | return innerHash(left, right); 68 | } 69 | } 70 | 71 | /** 72 | * @dev computes a Merkle tree where the leaves are the byte slice in the provided order 73 | * Follows RFC-6962 74 | */ 75 | function merkleRootHash(bytes[14] memory validators, uint start, uint total) internal pure returns (bytes32) { 76 | if (total == 0) { 77 | return emptyHash(); 78 | } else if (total == 1) { 79 | return leafHash(validators[start]); 80 | } else { 81 | uint k = getSplitPoint(total); 82 | bytes32 left = merkleRootHash(validators, start, k); // validators[:k] 83 | bytes32 right = merkleRootHash(validators, start+k, total-k); // validators[k:] 84 | return innerHash(left, right); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /contracts/utils/crypto/Secp256k1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: TBD 2 | 3 | pragma solidity ^0.8.2; 4 | 5 | import "../Bytes.sol"; 6 | 7 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 8 | 9 | library Secp256k1 { 10 | using Bytes for bytes; 11 | 12 | uint private constant _PUBKEY_BYTES_LEN_COMPRESSED = 33; 13 | uint8 private constant _PUBKEY_COMPRESSED = 0x2; 14 | uint8 private constant _PUBKEY_UNCOMPRESSED = 0x4; 15 | 16 | /** 17 | * @dev verifies the secp256k1 signature against the public key and message 18 | * Tendermint uses RFC6979 and BIP0062 standard, meaning there is no recovery bit ("v" argument) present in the signature. 19 | * The "v" argument is required by the ecrecover precompile (https://eips.ethereum.org/EIPS/eip-2098) and it can be either 0 or 1. 20 | * 21 | * To leverage the ecrecover precompile this method opportunisticly guess the "v" argument. At worst the precompile is called twice, 22 | * which still might be cheaper than running the verification in EVM bytecode (as solidity lib) 23 | * 24 | * See: tendermint/crypto/secp256k1/secp256k1_nocgo.go (Sign, Verify methods) 25 | */ 26 | function verify(bytes memory message, bytes memory publicKey, bytes memory signature) internal view returns (bool) { 27 | address signer = Bytes.toAddress(serializePubkey(publicKey, false)); 28 | bytes32 hash = sha256(message); 29 | (address recovered, ECDSA.RecoverError error) = tryRecover(hash, signature, 27); 30 | if (error == ECDSA.RecoverError.NoError && recovered != signer) { 31 | (recovered, error) = tryRecover(hash, signature, 28); 32 | } 33 | 34 | return error == ECDSA.RecoverError.NoError && recovered == signer; 35 | } 36 | 37 | /** 38 | * @dev returns the address that signed the hash. 39 | * This function flavor forces the "v" parameter instead of trying to derive it from the signature 40 | * 41 | * Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol#L56 42 | */ 43 | function tryRecover(bytes32 hash, bytes memory signature, uint8 v) internal pure returns (address, ECDSA.RecoverError) { 44 | if (signature.length == 65 || signature.length == 64) { 45 | bytes32 r; 46 | bytes32 s; 47 | // ecrecover takes the signature parameters, and the only way to get them 48 | // currently is to use assembly. 49 | assembly { 50 | r := mload(add(signature, 0x20)) 51 | s := mload(add(signature, 0x40)) 52 | } 53 | 54 | return ECDSA.tryRecover(hash, v, r, s); 55 | } else { 56 | return (address(0), ECDSA.RecoverError.InvalidSignatureLength); 57 | } 58 | } 59 | 60 | /** 61 | * @dev check if public key is compressed (length and format) 62 | */ 63 | function isCompressed(bytes memory pubkey) internal pure returns (bool) { 64 | return pubkey.length == _PUBKEY_BYTES_LEN_COMPRESSED && uint8(pubkey[0]) & 0xfe == _PUBKEY_COMPRESSED; 65 | } 66 | 67 | /** 68 | * @dev convert compressed PK to serialized-uncompressed format 69 | */ 70 | function serializePubkey(bytes memory pubkey, bool prefix) internal view returns (bytes memory) { 71 | require(isCompressed(pubkey), "Secp256k1: PK must be compressed"); 72 | 73 | uint8 yBit = uint8(pubkey[0]) & 1 == 1 ? 1 : 0; 74 | uint256 x = Bytes.toUint256(pubkey, 1); 75 | uint[2] memory xy = decompress(yBit, x); 76 | 77 | if (prefix) { 78 | return abi.encodePacked(_PUBKEY_UNCOMPRESSED, abi.encodePacked(xy[0]), abi.encodePacked(xy[1])); 79 | } 80 | 81 | return abi.encodePacked(abi.encodePacked(xy[0]), abi.encodePacked(xy[1])); 82 | } 83 | 84 | /** 85 | * @dev decompress a point 'Px', giving 'Py' for 'P = (Px, Py)' 86 | * 'yBit' is 1 if 'Qy' is odd, otherwise 0. 87 | * 88 | * Source: https://github.com/androlo/standard-contracts/blob/master/contracts/src/crypto/Secp256k1.sol#L82 89 | */ 90 | function decompress(uint8 yBit, uint x) internal view returns (uint[2] memory point) { 91 | uint p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; 92 | uint y2 = addmod(mulmod(x, mulmod(x, x, p), p), 7, p); 93 | uint y_ = modexp(y2, (p + 1) / 4, p); 94 | uint cmp = yBit ^ y_ & 1; 95 | point[0] = x; 96 | point[1] = (cmp == 0) ? y_ : p - y_; 97 | } 98 | 99 | /** 100 | * @dev modular exponentiation via EVM precompile (0x05) 101 | * 102 | * Source: https://docs.klaytn.com/smart-contract/precompiled-contracts#address-0x05-bigmodexp-base-exp-mod 103 | */ 104 | function modexp(uint base, uint exponent, uint modulus) internal view returns (uint result) { 105 | assembly { 106 | // free memory pointer 107 | let memPtr := mload(0x40) 108 | 109 | // length of base, exponent, modulus 110 | mstore(memPtr, 0x20) 111 | mstore(add(memPtr, 0x20), 0x20) 112 | mstore(add(memPtr, 0x40), 0x20) 113 | 114 | // assign base, exponent, modulus 115 | mstore(add(memPtr, 0x60), base) 116 | mstore(add(memPtr, 0x80), exponent) 117 | mstore(add(memPtr, 0xa0), modulus) 118 | 119 | // call the precompiled contract BigModExp (0x05) 120 | let success := staticcall(gas(), 0x05, memPtr, 0xc0, memPtr, 0x20) 121 | switch success 122 | case 0 { 123 | revert(0x0, 0x0) 124 | } default { 125 | result := mload(memPtr) 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | function deployMocks(deployer) { 2 | const Secp256k1Mock = artifacts.require("Secp256k1Mock"); 3 | const Ed25519Mock = artifacts.require("Ed25519Mock"); 4 | const MerkleTreeMock = artifacts.require("MerkleTreeMock"); 5 | const TendermintMock = artifacts.require("TendermintMock"); 6 | const ProtoMock = artifacts.require("ProtoMock"); 7 | 8 | deployer.deploy(Secp256k1Mock); 9 | deployer.deploy(Ed25519Mock); 10 | deployer.deploy(MerkleTreeMock); 11 | deployer.deploy(TendermintMock); 12 | deployer.deploy(ProtoMock); 13 | } 14 | 15 | function deployLightClient(deployer) { 16 | // contracts 17 | const IBCHost = artifacts.require("IBCHost"); 18 | const IBCClient = artifacts.require("IBCClient"); 19 | const IBCConnection = artifacts.require("IBCConnection"); 20 | const IBCChannel = artifacts.require("IBCChannel"); 21 | const IBCHandler = artifacts.require("IBCHandler"); 22 | const IBCMsgs = artifacts.require("IBCMsgs"); 23 | const IBCIdentifier = artifacts.require("IBCIdentifier"); 24 | const TendermintLightClient = artifacts.require("TendermintLightClient"); 25 | 26 | // libs 27 | const Bytes = artifacts.require("Bytes"); 28 | 29 | deployer.deploy(IBCIdentifier).then(function() { 30 | return deployer.link(IBCIdentifier, [IBCHost, TendermintLightClient, IBCHandler]); 31 | }); 32 | deployer.deploy(IBCMsgs).then(function() { 33 | return deployer.link(IBCMsgs, [IBCClient, IBCConnection, IBCChannel, IBCHandler, TendermintLightClient]); 34 | }); 35 | deployer.deploy(IBCClient).then(function() { 36 | return deployer.link(IBCClient, [IBCHandler, IBCConnection, IBCChannel]); 37 | }); 38 | deployer.deploy(IBCConnection).then(function() { 39 | return deployer.link(IBCConnection, [IBCHandler, IBCChannel]); 40 | }); 41 | deployer.deploy(IBCChannel).then(function() { 42 | return deployer.link(IBCChannel, [IBCHandler]); 43 | }); 44 | 45 | // TODO: truffle fails to deploy the library automatically, 46 | // explicit link solves the issue, but still not sure why this is 47 | // needed... it seems that Bytes is deployed as separate contract? 48 | deployer.deploy(Bytes); 49 | deployer.link(Bytes, TendermintLightClient); 50 | deployer.deploy(TendermintLightClient); 51 | 52 | deployer.deploy(IBCHost).then(function() { 53 | return deployer.deploy(IBCHandler, IBCHost.address); 54 | }); 55 | } 56 | 57 | module.exports = function(deployer, network) { 58 | if (network == 'tests') { 59 | deployMocks(deployer); 60 | } 61 | 62 | return deployLightClient(deployer); 63 | }; 64 | -------------------------------------------------------------------------------- /migrations/3_initialize_contract.js: -------------------------------------------------------------------------------- 1 | const IBCHost = artifacts.require("IBCHost"); 2 | const IBCHandler = artifacts.require("IBCHandler"); 3 | const TendermintLightClient = artifacts.require("TendermintLightClient"); 4 | 5 | const TendermintLightClientType = "07-tendermint" 6 | 7 | module.exports = async function (deployer) { 8 | const ibcHost = await IBCHost.deployed(); 9 | const ibcHandler = await IBCHandler.deployed(); 10 | 11 | for(const f of [ 12 | () => ibcHost.setIBCModule(IBCHandler.address), 13 | () => ibcHandler.registerClient(TendermintLightClientType, TendermintLightClient.address), 14 | ]) { 15 | const result = await f(); 16 | if(!result.receipt.status) { 17 | console.log(result); 18 | throw new Error(`transaction failed to execute. ${result.tx}`); 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@celo/contractkit": "^1.4.0", 4 | "@openzeppelin/contracts": "^4.3.2", 5 | "solidity-bytes-utils": "^0.8.0", 6 | "web3": "^1.6.1" 7 | }, 8 | "devDependencies": { 9 | "@truffle/hdwallet-provider": "^1.5.1", 10 | "ejs": "^3.1.6", 11 | "eslint": "^7.32.0", 12 | "eslint-config-standard": "^16.0.3", 13 | "eslint-plugin-import": "^2.25.2", 14 | "eslint-plugin-node": "^11.1.0", 15 | "eslint-plugin-promise": "^5.1.1", 16 | "prettier": "^2.4.1", 17 | "prettier-plugin-solidity": "^1.0.0-beta.18", 18 | "protobufjs": "^6.11.2", 19 | "truffle-assertions": "^0.9.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /proto/TendermintLight.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package tendermint.light; 3 | 4 | message Fraction { 5 | uint64 numerator = 1; 6 | uint64 denominator = 2; 7 | } 8 | 9 | // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp 10 | message Duration { 11 | int64 seconds = 1; 12 | int32 nanos = 2; 13 | } 14 | 15 | message Consensus { 16 | uint64 block = 1; 17 | uint64 app = 2; 18 | } 19 | 20 | message ClientState { 21 | string chain_id = 1; 22 | Fraction trust_level = 2; 23 | 24 | // duration of the period since the LastestTimestamp during which the 25 | // submitted headers are valid for upgrade 26 | Duration trusting_period = 3; 27 | // duration of the staking unbonding period 28 | Duration unbonding_period = 4; 29 | // defines how much new (untrusted) header's Time can drift into the future. 30 | Duration max_clock_drift = 5; 31 | // Block height when the client was frozen due to a misbehaviour 32 | //ibc.core.client.v1.Height frozen_height = 6; 33 | int64 frozen_height = 6; 34 | // Latest height the client was updated to 35 | int64 latest_height = 7; 36 | // This flag, when set to true, will allow governance to recover a client 37 | // which has expired 38 | bool allow_update_after_expiry = 8; 39 | // This flag, when set to true, will allow governance to unfreeze a client 40 | // whose chain has experienced a misbehaviour event 41 | bool allow_update_after_misbehaviour = 9; 42 | } 43 | 44 | // ConsensusState defines the consensus state from Tendermint. 45 | message ConsensusState { 46 | // timestamp that corresponds to the block height in which the ConsensusState 47 | // was stored. 48 | Timestamp timestamp = 1; 49 | 50 | // commitment root (i.e app hash) 51 | MerkleRoot root = 2; 52 | bytes next_validators_hash = 3; 53 | } 54 | 55 | // MerkleRoot defines a merkle root hash. 56 | // In the Cosmos SDK, the AppHash of a block header becomes the root. 57 | message MerkleRoot { 58 | bytes hash = 1; 59 | } 60 | 61 | enum BlockIDFlag { 62 | BLOCK_ID_FLAG_UNKNOWN = 0; 63 | BLOCK_ID_FLAG_ABSENT = 1; 64 | BLOCK_ID_FLAG_COMMIT = 2; 65 | BLOCK_ID_FLAG_NIL = 3; 66 | } 67 | 68 | enum SignedMsgType { 69 | SIGNED_MSG_TYPE_UNKNOWN = 0; 70 | // Votes 71 | SIGNED_MSG_TYPE_PREVOTE = 1; 72 | SIGNED_MSG_TYPE_PRECOMMIT = 2; 73 | 74 | // Proposals 75 | SIGNED_MSG_TYPE_PROPOSAL = 32; 76 | } 77 | 78 | message CanonicalPartSetHeader { 79 | uint32 total = 1; 80 | bytes hash = 2; 81 | } 82 | 83 | message CanonicalBlockID { 84 | bytes hash = 1; 85 | CanonicalPartSetHeader part_set_header = 2; 86 | } 87 | 88 | message CanonicalVote { 89 | SignedMsgType type = 1; 90 | sfixed64 height = 2; 91 | sfixed64 round = 3; 92 | CanonicalBlockID block_id = 4; 93 | Timestamp timestamp = 5; 94 | string chain_id = 6; 95 | } 96 | 97 | message Vote { 98 | SignedMsgType type = 1; 99 | int64 height = 2; 100 | int32 round = 3; 101 | BlockID block_id = 4; 102 | Timestamp timestamp = 5; 103 | bytes validator_address = 6; 104 | int32 validator_index = 7; 105 | bytes signature = 8; 106 | } 107 | 108 | message ValidatorSet { 109 | repeated Validator validators = 1; 110 | Validator proposer = 2; 111 | int64 total_voting_power = 3; 112 | } 113 | 114 | message Validator { 115 | bytes address = 1; 116 | PublicKey pub_key = 2; 117 | int64 voting_power = 3; 118 | int64 proposer_priority = 4; 119 | } 120 | 121 | message SimpleValidator { 122 | PublicKey pub_key = 1; 123 | int64 voting_power = 2; 124 | } 125 | 126 | message PublicKey { 127 | oneof sum { 128 | bytes ed25519 = 1; 129 | bytes secp256k1 = 2; 130 | bytes sr25519 = 3; 131 | } 132 | } 133 | 134 | message PartSetHeader { 135 | uint32 total = 1; 136 | bytes hash= 2; 137 | } 138 | 139 | message BlockID { 140 | bytes hash = 1; 141 | PartSetHeader part_set_header = 2; 142 | } 143 | 144 | message Commit { 145 | int64 height = 1; 146 | int32 round = 2; 147 | BlockID block_id = 3; 148 | repeated CommitSig signatures = 4; 149 | } 150 | 151 | // CommitSig is a part of the Vote included in a Commit. 152 | message CommitSig { 153 | BlockIDFlag block_id_flag = 1; 154 | bytes validator_address = 2; 155 | Timestamp timestamp = 3; 156 | bytes signature = 4; 157 | } 158 | 159 | message Timestamp { 160 | // Represents seconds of UTC time since Unix epoch 161 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 162 | // 9999-12-31T23:59:59Z inclusive. 163 | int64 seconds = 1; 164 | 165 | // Non-negative fractions of a second at nanosecond resolution. Negative 166 | // second values with fractions must still have non-negative nanos values 167 | // that count forward in time. Must be from 0 to 999,999,999 168 | // inclusive. 169 | int32 nanos = 2; 170 | } 171 | 172 | message LightHeader { 173 | Consensus version = 1; 174 | string chain_id = 2; 175 | int64 height = 3; 176 | Timestamp time = 4; 177 | BlockID last_block_id = 5; 178 | bytes last_commit_hash = 6; // commit from validators from the last block 179 | bytes data_hash = 7; // transactions 180 | bytes validators_hash = 8; // validators for the current block 181 | bytes next_validators_hash = 9; // validators for the next block 182 | bytes consensus_hash = 10; // consensus params for current block 183 | bytes app_hash = 11; // state after txs from the previous block 184 | bytes last_results_hash = 12; // root hash of all results from the txs from the previous block 185 | bytes evidence_hash = 13; // evidence included in the block 186 | bytes proposer_address = 14; // original proposer of the block 187 | } 188 | 189 | message SignedHeader { 190 | LightHeader header = 1; 191 | Commit commit = 2; 192 | } 193 | 194 | message TmHeader { 195 | SignedHeader signed_header = 1; 196 | ValidatorSet validator_set = 2; 197 | 198 | int64 trusted_height = 3; 199 | ValidatorSet trusted_validators = 4; 200 | } 201 | -------------------------------------------------------------------------------- /proto/ibc/Channel.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "pkg/ibc/channel"; 4 | 5 | 6 | // import "client/Client.proto"; 7 | 8 | // Channel defines pipeline for exactly-once packet delivery between specific 9 | // modules on separate blockchains, which has at least one end capable of 10 | // sending packets and one end capable of receiving packets. 11 | message Channel { 12 | 13 | 14 | // State defines if a channel is in one of the following states: 15 | // CLOSED, INIT, TRYOPEN, OPEN or UNINITIALIZED. 16 | enum State { 17 | 18 | 19 | // Default State 20 | STATE_UNINITIALIZED_UNSPECIFIED = 0; 21 | // A channel has just started the opening handshake. 22 | STATE_INIT = 1; 23 | // A channel has acknowledged the handshake step on the counterparty chain. 24 | STATE_TRYOPEN = 2; 25 | // A channel has completed the handshake. Open channels are 26 | // ready to send and receive packets. 27 | STATE_OPEN = 3; 28 | // A channel has been closed and can no longer be used to send or receive 29 | // packets. 30 | STATE_CLOSED = 4; 31 | } 32 | 33 | // Order defines if a channel is ORDERED or UNORDERED 34 | enum Order { 35 | 36 | 37 | // zero-value for channel ordering 38 | ORDER_NONE_UNSPECIFIED = 0; 39 | // packets can be delivered in any order, which may differ from the order in 40 | // which they were sent. 41 | ORDER_UNORDERED = 1; 42 | // packets are delivered exactly in the order which they were sent 43 | ORDER_ORDERED = 2; 44 | } 45 | 46 | // Counterparty defines a channel end counterparty 47 | message Counterparty { 48 | 49 | 50 | // port on the counterparty chain which owns the other end of the channel. 51 | string port_id = 1; 52 | // channel end on the counterparty chain 53 | string channel_id = 2; 54 | } 55 | 56 | // IdentifiedChannel defines a channel with additional port and channel 57 | // identifier fields. 58 | message IdentifiedChannel { 59 | 60 | 61 | // current state of the channel end 62 | State state = 1; 63 | // whether the channel is ordered or unordered 64 | Order ordering = 2; 65 | // counterparty channel end 66 | Counterparty counterparty = 3; 67 | // list of connection identifiers, in order, along which packets sent on 68 | // this channel will travel 69 | repeated string connection_hops = 4; 70 | // opaque channel version, which is agreed upon during the handshake 71 | string version = 5; 72 | // port identifier 73 | string port_id = 6; 74 | // channel identifier 75 | string channel_id = 7; 76 | } 77 | 78 | // current state of the channel end 79 | State state = 1; 80 | // whether the channel is ordered or unordered 81 | Order ordering = 2; 82 | // counterparty channel end 83 | Counterparty counterparty = 3; 84 | // list of connection identifiers, in order, along which packets sent on 85 | // this channel will travel 86 | repeated string connection_hops = 4; 87 | // opaque channel version, which is agreed upon during the handshake 88 | string version = 5; 89 | 90 | } 91 | 92 | // Packet defines a type that carries data across different chains through IBC 93 | message Packet { 94 | 95 | 96 | // number corresponds to the order of sends and receives, where a Packet 97 | // with an earlier sequence number must be sent and received before a Packet 98 | // with a later sequence number. 99 | uint64 sequence = 1; 100 | // identifies the port on the sending chain. 101 | string source_port = 2; 102 | // identifies the channel end on the sending chain. 103 | string source_channel = 3; 104 | // identifies the port on the receiving chain. 105 | string destination_port = 4; 106 | // identifies the channel end on the receiving chain. 107 | string destination_channel = 5; 108 | // actual opaque bytes transferred directly to the application module 109 | bytes data = 6; 110 | // block height after which the packet times out 111 | Height timeout_height = 7 112 | ; 113 | // block timestamp (in nanoseconds) after which the packet times out 114 | uint64 timeout_timestamp = 8; 115 | } 116 | 117 | // PacketState defines the generic type necessary to retrieve and store 118 | // packet commitments, acknowledgements, and receipts. 119 | // Caller is responsible for knowing the context necessary to interpret this 120 | // state as a commitment, acknowledgement, or a receipt. 121 | message PacketState { 122 | 123 | 124 | // channel port identifier. 125 | string port_id = 1; 126 | // channel unique identifier. 127 | string channel_id = 2; 128 | // packet sequence. 129 | uint64 sequence = 3; 130 | // embedded data that represents packet state. 131 | bytes data = 4; 132 | } 133 | 134 | // Height is a monotonically increasing data type 135 | // that can be compared against another Height for the purposes of updating and 136 | // freezing clients 137 | // 138 | // Normally the RevisionHeight is incremented at each height while keeping RevisionNumber 139 | // the same. However some consensus algorithms may choose to reset the 140 | // height in certain conditions e.g. hard forks, state-machine breaking changes 141 | // In these cases, the RevisionNumber is incremented so that height continues to 142 | // be monitonically increasing even as the RevisionHeight gets reset 143 | message Height { 144 | // the revision that the client is currently on 145 | uint64 revision_number = 1; 146 | // the height within the given revision 147 | uint64 revision_height = 2; 148 | } 149 | -------------------------------------------------------------------------------- /proto/ibc/Connection.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "pkg/ibc/connection"; 4 | 5 | // ConnectionEnd defines a stateful object on a chain connected to another 6 | // separate one. 7 | // NOTE: there must only be 2 defined ConnectionEnds to establish 8 | // a connection between two chains. 9 | message ConnectionEnd { 10 | // State defines if a connection is in one of the following states: 11 | // INIT, TRYOPEN, OPEN or UNINITIALIZED. 12 | enum State { 13 | // Default State 14 | STATE_UNINITIALIZED_UNSPECIFIED = 0; 15 | // A connection end has just started the opening handshake. 16 | STATE_INIT = 1; 17 | // A connection end has acknowledged the handshake step on the counterparty 18 | // chain. 19 | STATE_TRYOPEN = 2; 20 | // A connection end has completed the handshake. 21 | STATE_OPEN = 3; 22 | } 23 | 24 | // client associated with this connection. 25 | string client_id = 1; 26 | // IBC version which can be utilised to determine encodings or protocols for 27 | // channels or packets utilising this connection. 28 | repeated Version versions = 2; 29 | // current state of the connection end. 30 | State state = 3; 31 | // counterparty chain associated with this connection. 32 | Counterparty counterparty = 4; 33 | // delay period that must pass before a consensus state can be used for packet-verification 34 | // NOTE: delay period logic is only implemented by some clients. 35 | uint64 delay_period = 5; 36 | } 37 | 38 | // Counterparty defines the counterparty chain associated with a connection end. 39 | message Counterparty { 40 | // identifies the client on the counterparty chain associated with a given 41 | // connection. 42 | string client_id = 1; 43 | // identifies the connection end on the counterparty chain associated with a 44 | // given connection. 45 | string connection_id = 2; 46 | // commitment merkle prefix of the counterparty chain. 47 | MerklePrefix prefix = 3; 48 | } 49 | 50 | message MerklePrefix { 51 | bytes key_prefix = 1; 52 | } 53 | 54 | // Version defines the versioning scheme used to negotiate the IBC verison in 55 | // the connection handshake. 56 | message Version { 57 | // unique version identifier 58 | string identifier = 1; 59 | // list of features compatible with the specified identifier 60 | repeated string features = 2; 61 | } 62 | -------------------------------------------------------------------------------- /proto/ics23/proofs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | 4 | 5 | enum HashOp { 6 | // NO_HASH is the default if no data passed. Note this is an illegal argument some places. 7 | NO_HASH = 0; 8 | SHA256 = 1; 9 | SHA512 = 2; 10 | KECCAK = 3; 11 | RIPEMD160 = 4; 12 | BITCOIN = 5; // ripemd160(sha256(x)) 13 | SHA512_256 = 6; 14 | } 15 | 16 | /** 17 | LengthOp defines how to process the key and value of the LeafOp 18 | to include length information. After encoding the length with the given 19 | algorithm, the length will be prepended to the key and value bytes. 20 | (Each one with it's own encoded length) 21 | */ 22 | enum LengthOp { 23 | // NO_PREFIX don't include any length info 24 | NO_PREFIX = 0; 25 | // VAR_PROTO uses protobuf (and go-amino) varint encoding of the length 26 | VAR_PROTO = 1; 27 | // VAR_RLP uses rlp int encoding of the length 28 | VAR_RLP = 2; 29 | // FIXED32_BIG uses big-endian encoding of the length as a 32 bit integer 30 | FIXED32_BIG = 3; 31 | // FIXED32_LITTLE uses little-endian encoding of the length as a 32 bit integer 32 | FIXED32_LITTLE = 4; 33 | // FIXED64_BIG uses big-endian encoding of the length as a 64 bit integer 34 | FIXED64_BIG = 5; 35 | // FIXED64_LITTLE uses little-endian encoding of the length as a 64 bit integer 36 | FIXED64_LITTLE = 6; 37 | // REQUIRE_32_BYTES is like NONE, but will fail if the input is not exactly 32 bytes (sha256 output) 38 | REQUIRE_32_BYTES = 7; 39 | // REQUIRE_64_BYTES is like NONE, but will fail if the input is not exactly 64 bytes (sha512 output) 40 | REQUIRE_64_BYTES = 8; 41 | } 42 | 43 | /** 44 | ExistenceProof takes a key and a value and a set of steps to perform on it. 45 | The result of peforming all these steps will provide a "root hash", which can 46 | be compared to the value in a header. 47 | 48 | Since it is computationally infeasible to produce a hash collission for any of the used 49 | cryptographic hash functions, if someone can provide a series of operations to transform 50 | a given key and value into a root hash that matches some trusted root, these key and values 51 | must be in the referenced merkle tree. 52 | 53 | The only possible issue is maliablity in LeafOp, such as providing extra prefix data, 54 | which should be controlled by a spec. Eg. with lengthOp as NONE, 55 | prefix = FOO, key = BAR, value = CHOICE 56 | and 57 | prefix = F, key = OOBAR, value = CHOICE 58 | would produce the same value. 59 | 60 | With LengthOp this is tricker but not impossible. Which is why the "leafPrefixEqual" field 61 | in the ProofSpec is valuable to prevent this mutability. And why all trees should 62 | length-prefix the data before hashing it. 63 | */ 64 | message ExistenceProof { 65 | bytes key = 1; 66 | bytes value = 2; 67 | LeafOp leaf = 3; 68 | repeated InnerOp path = 4; 69 | } 70 | 71 | /* 72 | NonExistenceProof takes a proof of two neighbors, one left of the desired key, 73 | one right of the desired key. If both proofs are valid AND they are neighbors, 74 | then there is no valid proof for the given key. 75 | */ 76 | message NonExistenceProof { 77 | bytes key = 1; // TODO: remove this as unnecessary??? we prove a range 78 | ExistenceProof left = 2; 79 | ExistenceProof right = 3; 80 | } 81 | 82 | /* 83 | CommitmentProof is either an ExistenceProof or a NonExistenceProof, or a Batch of such messages 84 | */ 85 | message CommitmentProof { 86 | oneof proof { 87 | ExistenceProof exist = 1; 88 | NonExistenceProof nonexist = 2; 89 | BatchProof batch = 3; 90 | CompressedBatchProof compressed = 4; 91 | } 92 | } 93 | 94 | /** 95 | LeafOp represents the raw key-value data we wish to prove, and 96 | must be flexible to represent the internal transformation from 97 | the original key-value pairs into the basis hash, for many existing 98 | merkle trees. 99 | 100 | key and value are passed in. So that the signature of this operation is: 101 | leafOp(key, value) -> output 102 | 103 | To process this, first prehash the keys and values if needed (ANY means no hash in this case): 104 | hkey = prehashKey(key) 105 | hvalue = prehashValue(value) 106 | 107 | Then combine the bytes, and hash it 108 | output = hash(prefix || length(hkey) || hkey || length(hvalue) || hvalue) 109 | */ 110 | message LeafOp { 111 | HashOp hash = 1; 112 | HashOp prehash_key = 2; 113 | HashOp prehash_value = 3; 114 | LengthOp length = 4; 115 | // prefix is a fixed bytes that may optionally be included at the beginning to differentiate 116 | // a leaf node from an inner node. 117 | bytes prefix = 5; 118 | } 119 | 120 | /** 121 | InnerOp represents a merkle-proof step that is not a leaf. 122 | It represents concatenating two children and hashing them to provide the next result. 123 | 124 | The result of the previous step is passed in, so the signature of this op is: 125 | innerOp(child) -> output 126 | 127 | The result of applying InnerOp should be: 128 | output = op.hash(op.prefix || child || op.suffix) 129 | 130 | where the || operator is concatenation of binary data, 131 | and child is the result of hashing all the tree below this step. 132 | 133 | Any special data, like prepending child with the length, or prepending the entire operation with 134 | some value to differentiate from leaf nodes, should be included in prefix and suffix. 135 | If either of prefix or suffix is empty, we just treat it as an empty string 136 | */ 137 | message InnerOp { 138 | HashOp hash = 1; 139 | bytes prefix = 2; 140 | bytes suffix = 3; 141 | } 142 | 143 | 144 | /** 145 | ProofSpec defines what the expected parameters are for a given proof type. 146 | This can be stored in the client and used to validate any incoming proofs. 147 | 148 | verify(ProofSpec, Proof) -> Proof | Error 149 | 150 | As demonstrated in tests, if we don't fix the algorithm used to calculate the 151 | LeafHash for a given tree, there are many possible key-value pairs that can 152 | generate a given hash (by interpretting the preimage differently). 153 | We need this for proper security, requires client knows a priori what 154 | tree format server uses. But not in code, rather a configuration object. 155 | */ 156 | message ProofSpec { 157 | // any field in the ExistenceProof must be the same as in this spec. 158 | // except Prefix, which is just the first bytes of prefix (spec can be longer) 159 | LeafOp leaf_spec = 1; 160 | InnerSpec inner_spec = 2; 161 | // max_depth (if > 0) is the maximum number of InnerOps allowed (mainly for fixed-depth tries) 162 | int32 max_depth = 3; 163 | // min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries) 164 | int32 min_depth = 4; 165 | } 166 | 167 | /* 168 | InnerSpec contains all store-specific structure info to determine if two proofs from a 169 | given store are neighbors. 170 | 171 | This enables: 172 | 173 | isLeftMost(spec: InnerSpec, op: InnerOp) 174 | isRightMost(spec: InnerSpec, op: InnerOp) 175 | isLeftNeighbor(spec: InnerSpec, left: InnerOp, right: InnerOp) 176 | */ 177 | message InnerSpec { 178 | // Child order is the ordering of the children node, must count from 0 179 | // iavl tree is [0, 1] (left then right) 180 | // merk is [0, 2, 1] (left, right, here) 181 | repeated int32 child_order = 1; 182 | int32 child_size = 2; 183 | int32 min_prefix_length = 3; 184 | int32 max_prefix_length = 4; 185 | // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) 186 | bytes empty_child = 5; 187 | // hash is the algorithm that must be used for each InnerOp 188 | HashOp hash = 6; 189 | } 190 | 191 | /* 192 | BatchProof is a group of multiple proof types than can be compressed 193 | */ 194 | message BatchProof { 195 | repeated BatchEntry entries = 1; 196 | } 197 | 198 | // Use BatchEntry not CommitmentProof, to avoid recursion 199 | message BatchEntry { 200 | oneof proof { 201 | ExistenceProof exist = 1; 202 | NonExistenceProof nonexist = 2; 203 | } 204 | } 205 | 206 | 207 | /****** all items here are compressed forms *******/ 208 | 209 | message CompressedBatchProof { 210 | repeated CompressedBatchEntry entries = 1; 211 | repeated InnerOp lookup_inners = 2; 212 | } 213 | 214 | // Use BatchEntry not CommitmentProof, to avoid recursion 215 | message CompressedBatchEntry { 216 | oneof proof { 217 | CompressedExistenceProof exist = 1; 218 | CompressedNonExistenceProof nonexist = 2; 219 | } 220 | } 221 | 222 | message CompressedExistenceProof { 223 | bytes key = 1; 224 | bytes value = 2; 225 | LeafOp leaf = 3; 226 | // these are indexes into the lookup_inners table in CompressedBatchProof 227 | repeated int32 path = 4; 228 | } 229 | 230 | message CompressedNonExistenceProof { 231 | bytes key = 1; // TODO: remove this as unnecessary??? we prove a range 232 | CompressedExistenceProof left = 2; 233 | CompressedExistenceProof right = 3; 234 | } 235 | -------------------------------------------------------------------------------- /scripts/confgen.js: -------------------------------------------------------------------------------- 1 | const IBCHost = artifacts.require("IBCHost"); 2 | const IBCHandler = artifacts.require("IBCHandler"); 3 | const IBCIdentifier = artifacts.require("IBCIdentifier"); 4 | const TendermintLightClient = artifacts.require("TendermintLightClient"); 5 | 6 | var fs = require("fs"); 7 | var ejs = require("ejs"); 8 | 9 | if (!process.env.CONF_TPL) { 10 | console.log("You must set environment variable 'CONF_TPL'"); 11 | process.exit(1); 12 | } 13 | 14 | const makePairs = function(arr) { 15 | var pairs = []; 16 | for (var i=0 ; i /tmp/tmp.jq && mv /tmp/tmp.jq $f 5 | done 6 | -------------------------------------------------------------------------------- /scripts/getAccount.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const web3 = new Web3() 5 | 6 | const filePath = path.join(__dirname, './secret') 7 | 8 | function getAccount () { 9 | return new Promise(resolve => { 10 | if (fs.existsSync(filePath)) { 11 | fs.readFile(filePath, { encoding: 'utf-8' }, (err, data) => { 12 | resolve(web3.eth.accounts.privateKeyToAccount(data)) 13 | }) 14 | } else { 15 | const randomAccount = web3.eth.accounts.create() 16 | 17 | fs.writeFile(filePath, randomAccount.privateKey, (err) => { 18 | if (err) { 19 | return console.log(err) 20 | } 21 | }) 22 | 23 | resolve(randomAccount) 24 | } 25 | }) 26 | } 27 | 28 | module.exports = { 29 | getAccount 30 | } 31 | -------------------------------------------------------------------------------- /scripts/import_ibc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # NOTE: This scripts imports yui-ibc-solidity (IBC Handlers) to the repo. Why? 4 | # 1. The protobuf files must be compiled with the same compiler 5 | # 2. We are only interested in IBC Handlers (nothing else) 6 | # 3. We use newer solidity compiler (see pragma) 7 | # 8 | # This aims to be temporary and we should work to make the IBC handlers a common library 9 | 10 | set -eux 11 | 12 | rm -rf ./yui-ibc-solidity 13 | git clone https://github.com/hyperledger-labs/yui-ibc-solidity.git 2>/dev/null 14 | cd yui-ibc-solidity && git checkout dc2538d4ac851a0c5588a2a23d0a0ca1e9f3b039 15 | 16 | # STEP 1: copy proto files 17 | mkdir -p ../proto/ibc 18 | 19 | cp -r ./proto/channel/Channel.proto ../proto/ibc/ 20 | cp -r ./proto/connection/Connection.proto ../proto/ibc/ 21 | 22 | # remove gogoproto references 23 | for proto_file in \ 24 | ../proto/ibc/Channel.proto \ 25 | ../proto/ibc/Connection.proto \ 26 | ; do 27 | sed -i "s/\s\[(gogoproto.*\];/;/g" $proto_file 28 | sed -i "s/option (gogoproto.*//g" $proto_file 29 | sed -i "s/import \"gogoproto.*//g" $proto_file 30 | done; 31 | 32 | # STEP 2: copy IBC core libraries 33 | mkdir -p ../contracts/ibc 34 | 35 | for core_lib_file in \ 36 | IBCChannel.sol \ 37 | IBCClient.sol \ 38 | IBCConnection.sol \ 39 | IBCHandler.sol \ 40 | IBCHost.sol \ 41 | IBCIdentifier.sol \ 42 | IBCModule.sol \ 43 | IBCMsgs.sol \ 44 | IClient.sol \ 45 | ; do 46 | cp contracts/core/$core_lib_file ../contracts/ibc/ 47 | sed -i "s/pragma experimental ABIEncoderV2;//g" ../contracts/ibc/$core_lib_file 48 | sed -i "s/pragma solidity.*/pragma solidity ^0.8.2;/g" ../contracts/ibc/$core_lib_file 49 | 50 | sed -i "s/\.\/types\/Channel.sol/\.\.\/proto\/Channel.sol/g" ../contracts/ibc/$core_lib_file 51 | sed -i "s/\.\/types\/Connection.sol/\.\.\/proto\/Connection.sol/g" ../contracts/ibc/$core_lib_file 52 | 53 | echo -e "// SPDX-License-Identifier: TBD\n// Source: https://github.com/hyperledger-labs/yui-ibc-solidity\n$(cat ../contracts/ibc/${core_lib_file})" > ../contracts/ibc/$core_lib_file 54 | done; 55 | -------------------------------------------------------------------------------- /scripts/import_ics23.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # NOTE: This script imports ics23 repo (ICS-23 Proofs) to the repo. Why? 4 | # 1. The protobuf files must be compiled with the same compiler 5 | # 2. The ICS23 branch is still under development 6 | # 7 | # This aims to be a temporary solution 8 | 9 | set -eux 10 | 11 | rm -rf ./ics23 12 | git clone https://github.com/ChorusOne/ics23.git 13 | cd ics23 && git checkout 2ce6204405b60de138e21a0ca9848921e1bd4d1c 14 | 15 | # STEP 1: copy proto files 16 | mkdir -p ../proto/ics23 17 | 18 | cp -r ./proofs.proto ../proto/ics23/ 19 | 20 | # remove package (sol compiler prefixes the struct names with it) 21 | sed -i "s/package .*//g" ../proto/ics23/proofs.proto 22 | 23 | # STEP 2: copy IBC core libraries 24 | mkdir -p ../contracts/ics23 25 | 26 | for core_lib_file in \ 27 | ics23.sol \ 28 | ics23Compress.sol \ 29 | ics23Ops.sol \ 30 | ics23Proof.sol \ 31 | ; do 32 | cp sol/contracts/$core_lib_file ../contracts/ics23/ 33 | sed -i "s/pragma experimental ABIEncoderV2;//g" ../contracts/ics23/$core_lib_file 34 | sed -i "s/pragma solidity.*/pragma solidity ^0.8.2;/g" ../contracts/ics23/$core_lib_file 35 | 36 | sed -i "s/bytes.concat(/abi.encodePacked(/g" ../contracts/ics23/$core_lib_file 37 | sed -i "s/\.\/proofs\.sol/\.\.\/proto\/proofs.sol/g" ../contracts/ics23/$core_lib_file 38 | sed -i "s/OpenZeppelin\/openzeppelin-contracts@4.2.0/@openzeppelin/g" ../contracts/ics23/$core_lib_file 39 | sed -i "s/GNSPS\/solidity-bytes-utils@0.8.0/solidity-bytes-utils/g" ../contracts/ics23/$core_lib_file 40 | sed -i "s/\.\/ProtoBufRuntime.sol/\.\.\/proto\/ProtoBufRuntime.sol/g" ../contracts/ics23/$core_lib_file 41 | 42 | echo -e "// SPDX-License-Identifier: TBD\n// Source: https://github.com/ChorusOne/ics23/tree/giulio/solidity\n$(cat ../contracts/ics23/${core_lib_file})" > ../contracts/ics23/$core_lib_file 43 | done; 44 | -------------------------------------------------------------------------------- /scripts/protobuf_compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | SOLPB_DIR=./solidity-protobuf 6 | 7 | TMP=$(mktemp -d) 8 | 9 | for file in $(find ./proto -name '*.proto') 10 | do 11 | echo "Generating ${file}" 12 | 13 | FNAME=$(basename $file) 14 | 15 | # remove package (issue with protobuf enum prefix) 16 | sed "s/package tendermint.*;//g" $file > $TMP/$FNAME 17 | 18 | protoc \ 19 | -I$TMP \ 20 | -I${SOLPB_DIR}/protobuf-solidity/src/protoc/include \ 21 | --plugin=protoc-gen-sol=${SOLPB_DIR}/protobuf-solidity/src/protoc/plugin/gen_sol.py \ 22 | --"sol_out=gen_runtime=ProtoBufRuntime.sol&solc_version=0.8.2:$(pwd)/contracts/proto/" \ 23 | $TMP/$FNAME 24 | done 25 | -------------------------------------------------------------------------------- /scripts/template/contract.rs.tpl: -------------------------------------------------------------------------------- 1 | pub const IBC_HOST_ADDRESS: &str = "<%= IBCHostAddress; %>"; 2 | pub const IBC_HANDLER_ADDRESS: &str = "<%= IBCHandlerAddress; %>"; 3 | pub const IBC_IDENTIFIER_ADDRESS: &str = "<%= IBCIdentifierAddress; %>"; 4 | pub const TENDERMINT_LIGHT_CLIENT_ADDRESS: &str = "<%= TendermintLightClientAddress; %>"; 5 | -------------------------------------------------------------------------------- /test/Proto.test.js: -------------------------------------------------------------------------------- 1 | const ProtoMock = artifacts.require('ProtoMock') 2 | const protobuf = require('protobufjs') 3 | const lib = require('./lib.js') 4 | 5 | contract('ProtoMock', () => { 6 | it('verifies TmHeader deserialization (with trusted_validator_set)', async () => { 7 | await deserialize(8619996, 8619997, true) 8 | }) 9 | 10 | it('verifies TmHeader deserialization (without trusted_validator_set)', async () => { 11 | await deserialize(8619996, 8619997, false) 12 | }) 13 | }) 14 | 15 | async function deserialize (h1, h2, with_trusted) { 16 | const root = new protobuf.Root() 17 | let Any 18 | 19 | await root.load('test/data/any.proto', { keepCase: true }).then(async function (root, err) { 20 | if (err) { 21 | throw err 22 | } 23 | 24 | Any = root.lookupType('Any') 25 | }) 26 | 27 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 28 | if (err) { throw err } 29 | 30 | // types 31 | const TmHeader = root.lookupType('tendermint.light.TmHeader') 32 | const ValidatorSet = root.lookupType('tendermint.light.ValidatorSet') 33 | 34 | // core structs 35 | const [sh, vs] = await lib.readHeader(h1) 36 | const [ssh, svs] = await lib.readHeader(h2) 37 | 38 | // contracts 39 | const protoMock = await ProtoMock.deployed() 40 | 41 | let tmHeader 42 | if (with_trusted) { 43 | tmHeader = TmHeader.create({ 44 | signed_header: ssh, 45 | validator_set: svs, 46 | 47 | trusted_height: sh.header.height.low, 48 | trusted_validators: vs 49 | }) 50 | } else { 51 | tmHeader = TmHeader.create({ 52 | signed_header: ssh, 53 | validator_set: svs, 54 | 55 | trusted_height: sh.header.height.low, 56 | trusted_validator_set: (() => { 57 | const vsObj = vs 58 | vsObj.validators = [] 59 | return ValidatorSet.fromObject(vsObj) 60 | })() 61 | }) 62 | } 63 | 64 | const all = Any.create({ 65 | value: await TmHeader.encode(tmHeader).finish(), 66 | type_url: '/tendermint.types.TmHeader' 67 | }) 68 | const allSerialized = await Any.encode(all).finish() 69 | 70 | await lib.call(async () => { 71 | return await protoMock.unmarshalHeader( 72 | allSerialized, 73 | ssh.header.chain_id 74 | ) 75 | }, 'failed to call unmarshalHeader') 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /test/TendermintLightClient.test.js: -------------------------------------------------------------------------------- 1 | const TendermintLightClient = artifacts.require('TendermintLightClient') 2 | const IBCHandler = artifacts.require('IBCHandler') 3 | const IBCHost = artifacts.require('IBCHost') 4 | const protobuf = require('protobufjs') 5 | const lib = require('./lib.js') 6 | 7 | contract('TendermintLightClient', () => { 8 | it('verifies ingestion of valid continuous headers', async () => { 9 | await ingest(8619996, 8619997) 10 | }) 11 | 12 | it('verifies ingestion of valid non-continuous headers', async () => { 13 | await ingest(8619996, 8619998) 14 | }) 15 | }) 16 | 17 | async function ingest(h1, h2) { 18 | const root = new protobuf.Root() 19 | let Any 20 | 21 | await root.load('test/data/any.proto', { keepCase: true }).then(async function (root, err) { 22 | if (err) { 23 | throw err 24 | } 25 | 26 | Any = root.lookupType('Any') 27 | }) 28 | 29 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 30 | if (err) { throw err } 31 | 32 | // types 33 | const ClientState = root.lookupType('tendermint.light.ClientState') 34 | const ConsensusState = root.lookupType('tendermint.light.ConsensusState') 35 | const TmHeader = root.lookupType('tendermint.light.TmHeader') 36 | const Fraction = root.lookupType('tendermint.light.Fraction') 37 | const Duration = root.lookupType('tendermint.light.Duration') 38 | 39 | // core structs 40 | const [sh, vs] = await lib.readHeader(h1) 41 | const [ssh, svs] = await lib.readHeader(h2) 42 | 43 | // args 44 | const clientStateObj = ClientState.create({ 45 | chain_id: sh.header.chain_id, 46 | trust_level: Fraction.create({ 47 | numerator: 1, 48 | denominator: 3 49 | }), 50 | trusting_period: Duration.create({ 51 | seconds: 100000000000, 52 | nanos: 0 53 | }), 54 | unbonding_period: Duration.create({ 55 | seconds: 100000000000, 56 | nanos: 0 57 | }), 58 | max_clock_drift: Duration.create({ 59 | seconds: 100000000000, 60 | nanos: 0 61 | }), 62 | frozen_height: 0, 63 | latest_height: sh.header.height, 64 | allow_update_after_expiry: true, 65 | allow_update_after_misbehaviour: true 66 | }) 67 | 68 | const consensusStateObj = ConsensusState.create({ 69 | root: sh.header.app_hash, 70 | timestamp: sh.header.time, 71 | next_validators_hash: sh.header.next_validators_hash 72 | }) 73 | 74 | // encoded args 75 | const encodedClientState = await Any.encode(Any.create({ 76 | value: await ClientState.encode(clientStateObj).finish(), 77 | type_url: '/tendermint.types.ClientState' 78 | })).finish() 79 | 80 | const encodedConsensusState = await Any.encode(Any.create({ 81 | value: await ConsensusState.encode(consensusStateObj).finish(), 82 | type_url: '/tendermint.types.ConsensusState' 83 | })).finish() 84 | 85 | // contracts 86 | const tlc = await TendermintLightClient.deployed() 87 | const handler = await IBCHandler.deployed() 88 | const host = await IBCHost.deployed() 89 | 90 | // step 1: register client 91 | try { 92 | await handler.registerClient.call('07-tendermint', tlc.address) 93 | } catch (error) { 94 | if (!error.message.includes('clientImpl already exists')) { 95 | throw error 96 | } 97 | } 98 | 99 | // step 2: create client 100 | await lib.call(async () => { 101 | return await handler.createClient({ 102 | clientType: '07-tendermint', 103 | height: sh.header.height.low, 104 | clientStateBytes: encodedClientState, 105 | consensusStateBytes: encodedConsensusState 106 | }) 107 | }, "failed to call createClient"); 108 | 109 | // step 3: get client id 110 | const events = await host.getPastEvents('GeneratedClientIdentifier') 111 | const clientId = events[events.length - 1].returnValues['0'] 112 | 113 | // step 4: update client 114 | const tmHeader = TmHeader.create({ 115 | signed_header: ssh, 116 | validator_set: svs, 117 | 118 | trusted_height: sh.header.height.low, 119 | trusted_validators: vs 120 | }) 121 | 122 | const all = Any.create({ 123 | value: await TmHeader.encode(tmHeader).finish(), 124 | type_url: '/tendermint.types.TmHeader' 125 | }) 126 | const allSerialized = await Any.encode(all).finish() 127 | 128 | await lib.call(async () => { 129 | return await handler.updateClient({ 130 | clientId: clientId, 131 | header: allSerialized 132 | }); 133 | }, "failed to call updateClient"); 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /test/data/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/anypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := anypb.New(foo) 81 | // if err != nil { 82 | // ... 83 | // } 84 | // ... 85 | // foo := &pb.Foo{} 86 | // if err := any.UnmarshalTo(foo); err != nil { 87 | // ... 88 | // } 89 | // 90 | // The pack methods provided by protobuf library will by default use 91 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 92 | // methods only use the fully qualified type name after the last '/' 93 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 94 | // name "y.z". 95 | // 96 | // 97 | // JSON 98 | // 99 | // The JSON representation of an `Any` value uses the regular 100 | // representation of the deserialized, embedded message, with an 101 | // additional field `@type` which contains the type URL. Example: 102 | // 103 | // package google.profile; 104 | // message Person { 105 | // string first_name = 1; 106 | // string last_name = 2; 107 | // } 108 | // 109 | // { 110 | // "@type": "type.googleapis.com/google.profile.Person", 111 | // "firstName": , 112 | // "lastName": 113 | // } 114 | // 115 | // If the embedded message type is well-known and has a custom JSON 116 | // representation, that representation will be embedded adding a field 117 | // `value` which holds the custom JSON in addition to the `@type` 118 | // field. Example (for message [google.protobuf.Duration][]): 119 | // 120 | // { 121 | // "@type": "type.googleapis.com/google.protobuf.Duration", 122 | // "value": "1.212s" 123 | // } 124 | // 125 | message Any { 126 | // A URL/resource name that uniquely identifies the type of the serialized 127 | // protocol buffer message. This string must contain at least 128 | // one "/" character. The last segment of the URL's path must represent 129 | // the fully qualified name of the type (as in 130 | // `path/google.protobuf.Duration`). The name should be in a canonical form 131 | // (e.g., leading "." is not accepted). 132 | // 133 | // In practice, teams usually precompile into the binary all types that they 134 | // expect it to use in the context of Any. However, for URLs which use the 135 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 136 | // server that maps type URLs to message definitions as follows: 137 | // 138 | // * If no scheme is provided, `https` is assumed. 139 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 140 | // value in binary format, or produce an error. 141 | // * Applications are allowed to cache lookup results based on the 142 | // URL, or have them precompiled into a binary to avoid any 143 | // lookup. Therefore, binary compatibility needs to be preserved 144 | // on changes to types. (Use versioned type names to manage 145 | // breaking changes.) 146 | // 147 | // Note: this functionality is not currently available in the official 148 | // protobuf release, and it is not used for type URLs beginning with 149 | // type.googleapis.com. 150 | // 151 | // Schemes other than `http`, `https` (or the empty scheme) might be 152 | // used with implementation specific semantics. 153 | // 154 | string type_url = 1; 155 | 156 | // Must be a valid serialized protocol buffer of the above specified type. 157 | bytes value = 2; 158 | } 159 | -------------------------------------------------------------------------------- /test/data/header.28.signed_header.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "version": { 4 | "block": 11, 5 | "app": 0 6 | }, 7 | "chain_id": "wormhole", 8 | "height": 28, 9 | "time": { 10 | "seconds": 1634765002, 11 | "nanos": 453715295 12 | }, 13 | "last_block_id": { 14 | "hash": [ 15 | 195, 16 | 208, 17 | 143, 18 | 43, 19 | 152, 20 | 13, 21 | 12, 22 | 81, 23 | 62, 24 | 165, 25 | 167, 26 | 64, 27 | 26, 28 | 30, 29 | 121, 30 | 166, 31 | 206, 32 | 175, 33 | 198, 34 | 199, 35 | 9, 36 | 136, 37 | 73, 38 | 24, 39 | 34, 40 | 47, 41 | 156, 42 | 144, 43 | 50, 44 | 12, 45 | 54, 46 | 74 47 | ], 48 | "part_set_header": { 49 | "total": 1, 50 | "hash": [ 51 | 159, 52 | 129, 53 | 214, 54 | 45, 55 | 67, 56 | 249, 57 | 14, 58 | 237, 59 | 162, 60 | 89, 61 | 121, 62 | 117, 63 | 73, 64 | 254, 65 | 152, 66 | 34, 67 | 70, 68 | 52, 69 | 229, 70 | 224, 71 | 34, 72 | 173, 73 | 73, 74 | 104, 75 | 230, 76 | 197, 77 | 76, 78 | 247, 79 | 131, 80 | 122, 81 | 129, 82 | 208 83 | ] 84 | } 85 | }, 86 | "last_commit_hash": [ 87 | 10, 88 | 225, 89 | 226, 90 | 44, 91 | 57, 92 | 160, 93 | 248, 94 | 28, 95 | 239, 96 | 94, 97 | 169, 98 | 194, 99 | 72, 100 | 69, 101 | 93, 102 | 156, 103 | 52, 104 | 205, 105 | 130, 106 | 79, 107 | 224, 108 | 92, 109 | 45, 110 | 237, 111 | 146, 112 | 21, 113 | 162, 114 | 251, 115 | 223, 116 | 181, 117 | 19, 118 | 12 119 | ], 120 | "data_hash": [ 121 | 227, 122 | 176, 123 | 196, 124 | 66, 125 | 152, 126 | 252, 127 | 28, 128 | 20, 129 | 154, 130 | 251, 131 | 244, 132 | 200, 133 | 153, 134 | 111, 135 | 185, 136 | 36, 137 | 39, 138 | 174, 139 | 65, 140 | 228, 141 | 100, 142 | 155, 143 | 147, 144 | 76, 145 | 164, 146 | 149, 147 | 153, 148 | 27, 149 | 120, 150 | 82, 151 | 184, 152 | 85 153 | ], 154 | "validators_hash": [ 155 | 18, 156 | 158, 157 | 139, 158 | 169, 159 | 58, 160 | 185, 161 | 247, 162 | 69, 163 | 87, 164 | 175, 165 | 61, 166 | 30, 167 | 24, 168 | 13, 169 | 132, 170 | 76, 171 | 219, 172 | 211, 173 | 187, 174 | 70, 175 | 244, 176 | 141, 177 | 214, 178 | 35, 179 | 173, 180 | 8, 181 | 84, 182 | 175, 183 | 121, 184 | 103, 185 | 254, 186 | 242 187 | ], 188 | "next_validators_hash": [ 189 | 18, 190 | 158, 191 | 139, 192 | 169, 193 | 58, 194 | 185, 195 | 247, 196 | 69, 197 | 87, 198 | 175, 199 | 61, 200 | 30, 201 | 24, 202 | 13, 203 | 132, 204 | 76, 205 | 219, 206 | 211, 207 | 187, 208 | 70, 209 | 244, 210 | 141, 211 | 214, 212 | 35, 213 | 173, 214 | 8, 215 | 84, 216 | 175, 217 | 121, 218 | 103, 219 | 254, 220 | 242 221 | ], 222 | "consensus_hash": [ 223 | 4, 224 | 128, 225 | 145, 226 | 188, 227 | 125, 228 | 220, 229 | 40, 230 | 63, 231 | 119, 232 | 191, 233 | 191, 234 | 145, 235 | 215, 236 | 60, 237 | 68, 238 | 218, 239 | 88, 240 | 195, 241 | 223, 242 | 138, 243 | 156, 244 | 188, 245 | 134, 246 | 116, 247 | 5, 248 | 216, 249 | 183, 250 | 243, 251 | 218, 252 | 173, 253 | 162, 254 | 47 255 | ], 256 | "app_hash": [ 257 | 110, 258 | 71, 259 | 240, 260 | 233, 261 | 160, 262 | 211, 263 | 230, 264 | 7, 265 | 102, 266 | 70, 267 | 100, 268 | 237, 269 | 78, 270 | 222, 271 | 139, 272 | 110, 273 | 25, 274 | 6, 275 | 60, 276 | 99, 277 | 53, 278 | 57, 279 | 42, 280 | 181, 281 | 156, 282 | 192, 283 | 64, 284 | 14, 285 | 245, 286 | 66, 287 | 64, 288 | 195 289 | ], 290 | "last_results_hash": [ 291 | 227, 292 | 176, 293 | 196, 294 | 66, 295 | 152, 296 | 252, 297 | 28, 298 | 20, 299 | 154, 300 | 251, 301 | 244, 302 | 200, 303 | 153, 304 | 111, 305 | 185, 306 | 36, 307 | 39, 308 | 174, 309 | 65, 310 | 228, 311 | 100, 312 | 155, 313 | 147, 314 | 76, 315 | 164, 316 | 149, 317 | 153, 318 | 27, 319 | 120, 320 | 82, 321 | 184, 322 | 85 323 | ], 324 | "evidence_hash": [ 325 | 227, 326 | 176, 327 | 196, 328 | 66, 329 | 152, 330 | 252, 331 | 28, 332 | 20, 333 | 154, 334 | 251, 335 | 244, 336 | 200, 337 | 153, 338 | 111, 339 | 185, 340 | 36, 341 | 39, 342 | 174, 343 | 65, 344 | 228, 345 | 100, 346 | 155, 347 | 147, 348 | 76, 349 | 164, 350 | 149, 351 | 153, 352 | 27, 353 | 120, 354 | 82, 355 | 184, 356 | 85 357 | ], 358 | "proposer_address": [ 359 | 86, 360 | 228, 361 | 152, 362 | 158, 363 | 92, 364 | 202, 365 | 108, 366 | 77, 367 | 59, 368 | 28, 369 | 213, 370 | 105, 371 | 128, 372 | 241, 373 | 174, 374 | 87, 375 | 41, 376 | 69, 377 | 60, 378 | 117 379 | ] 380 | }, 381 | "commit": { 382 | "height": 28, 383 | "round": 0, 384 | "block_id": { 385 | "hash": [ 386 | 125, 387 | 194, 388 | 48, 389 | 148, 390 | 151, 391 | 113, 392 | 212, 393 | 135, 394 | 15, 395 | 146, 396 | 3, 397 | 200, 398 | 180, 399 | 187, 400 | 99, 401 | 203, 402 | 119, 403 | 185, 404 | 132, 405 | 93, 406 | 209, 407 | 93, 408 | 7, 409 | 142, 410 | 203, 411 | 216, 412 | 92, 413 | 73, 414 | 23, 415 | 61, 416 | 129, 417 | 20 418 | ], 419 | "part_set_header": { 420 | "total": 1, 421 | "hash": [ 422 | 55, 423 | 134, 424 | 230, 425 | 12, 426 | 244, 427 | 170, 428 | 40, 429 | 220, 430 | 13, 431 | 180, 432 | 144, 433 | 205, 434 | 150, 435 | 172, 436 | 56, 437 | 222, 438 | 136, 439 | 6, 440 | 154, 441 | 14, 442 | 255, 443 | 197, 444 | 240, 445 | 88, 446 | 128, 447 | 176, 448 | 43, 449 | 13, 450 | 35, 451 | 254, 452 | 225, 453 | 40 454 | ] 455 | } 456 | }, 457 | "signatures": [ 458 | { 459 | "block_id_flag": 2, 460 | "validator_address": [ 461 | 86, 462 | 228, 463 | 152, 464 | 158, 465 | 92, 466 | 202, 467 | 108, 468 | 77, 469 | 59, 470 | 28, 471 | 213, 472 | 105, 473 | 128, 474 | 241, 475 | 174, 476 | 87, 477 | 41, 478 | 69, 479 | 60, 480 | 117 481 | ], 482 | "timestamp": { 483 | "seconds": 1634765007, 484 | "nanos": 501429636 485 | }, 486 | "signature": [ 487 | 206, 488 | 211, 489 | 13, 490 | 241, 491 | 220, 492 | 133, 493 | 228, 494 | 174, 495 | 159, 496 | 113, 497 | 171, 498 | 87, 499 | 7, 500 | 94, 501 | 182, 502 | 35, 503 | 54, 504 | 112, 505 | 135, 506 | 20, 507 | 85, 508 | 198, 509 | 199, 510 | 87, 511 | 97, 512 | 241, 513 | 86, 514 | 150, 515 | 255, 516 | 235, 517 | 228, 518 | 82, 519 | 69, 520 | 47, 521 | 204, 522 | 190, 523 | 220, 524 | 59, 525 | 172, 526 | 106, 527 | 34, 528 | 244, 529 | 78, 530 | 94, 531 | 127, 532 | 155, 533 | 133, 534 | 11, 535 | 227, 536 | 69, 537 | 226, 538 | 106, 539 | 116, 540 | 58, 541 | 17, 542 | 3, 543 | 142, 544 | 218, 545 | 79, 546 | 215, 547 | 125, 548 | 21, 549 | 229, 550 | 3 551 | ] 552 | } 553 | ] 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /test/data/header.28.validator_set.json: -------------------------------------------------------------------------------- 1 | { 2 | "validators": [ 3 | { 4 | "address": [ 5 | 86, 6 | 228, 7 | 152, 8 | 158, 9 | 92, 10 | 202, 11 | 108, 12 | 77, 13 | 59, 14 | 28, 15 | 213, 16 | 105, 17 | 128, 18 | 241, 19 | 174, 20 | 87, 21 | 41, 22 | 69, 23 | 60, 24 | 117 25 | ], 26 | "pub_key": { 27 | "ed25519": [ 28 | 119, 29 | 198, 30 | 146, 31 | 235, 32 | 33, 33 | 246, 34 | 5, 35 | 2, 36 | 212, 37 | 34, 38 | 21, 39 | 243, 40 | 254, 41 | 119, 42 | 90, 43 | 111, 44 | 118, 45 | 160, 46 | 155, 47 | 161, 48 | 114, 49 | 56, 50 | 69, 51 | 34, 52 | 19, 53 | 77, 54 | 229, 55 | 243, 56 | 106, 57 | 160, 58 | 4, 59 | 208 60 | ] 61 | }, 62 | "voting_power": 100000, 63 | "proposer_priority": 0 64 | } 65 | ], 66 | "proposer": null, 67 | "total_voting_power": 0 68 | } 69 | -------------------------------------------------------------------------------- /test/data/header.29.signed_header.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "version": { 4 | "block": 11, 5 | "app": 0 6 | }, 7 | "chain_id": "wormhole", 8 | "height": 29, 9 | "time": { 10 | "seconds": 1634765007, 11 | "nanos": 501429636 12 | }, 13 | "last_block_id": { 14 | "hash": [ 15 | 125, 16 | 194, 17 | 48, 18 | 148, 19 | 151, 20 | 113, 21 | 212, 22 | 135, 23 | 15, 24 | 146, 25 | 3, 26 | 200, 27 | 180, 28 | 187, 29 | 99, 30 | 203, 31 | 119, 32 | 185, 33 | 132, 34 | 93, 35 | 209, 36 | 93, 37 | 7, 38 | 142, 39 | 203, 40 | 216, 41 | 92, 42 | 73, 43 | 23, 44 | 61, 45 | 129, 46 | 20 47 | ], 48 | "part_set_header": { 49 | "total": 1, 50 | "hash": [ 51 | 55, 52 | 134, 53 | 230, 54 | 12, 55 | 244, 56 | 170, 57 | 40, 58 | 220, 59 | 13, 60 | 180, 61 | 144, 62 | 205, 63 | 150, 64 | 172, 65 | 56, 66 | 222, 67 | 136, 68 | 6, 69 | 154, 70 | 14, 71 | 255, 72 | 197, 73 | 240, 74 | 88, 75 | 128, 76 | 176, 77 | 43, 78 | 13, 79 | 35, 80 | 254, 81 | 225, 82 | 40 83 | ] 84 | } 85 | }, 86 | "last_commit_hash": [ 87 | 25, 88 | 140, 89 | 46, 90 | 232, 91 | 75, 92 | 203, 93 | 112, 94 | 157, 95 | 39, 96 | 101, 97 | 70, 98 | 190, 99 | 201, 100 | 106, 101 | 160, 102 | 136, 103 | 139, 104 | 49, 105 | 169, 106 | 74, 107 | 219, 108 | 124, 109 | 196, 110 | 122, 111 | 36, 112 | 238, 113 | 209, 114 | 15, 115 | 40, 116 | 239, 117 | 157, 118 | 171 119 | ], 120 | "data_hash": [ 121 | 227, 122 | 176, 123 | 196, 124 | 66, 125 | 152, 126 | 252, 127 | 28, 128 | 20, 129 | 154, 130 | 251, 131 | 244, 132 | 200, 133 | 153, 134 | 111, 135 | 185, 136 | 36, 137 | 39, 138 | 174, 139 | 65, 140 | 228, 141 | 100, 142 | 155, 143 | 147, 144 | 76, 145 | 164, 146 | 149, 147 | 153, 148 | 27, 149 | 120, 150 | 82, 151 | 184, 152 | 85 153 | ], 154 | "validators_hash": [ 155 | 18, 156 | 158, 157 | 139, 158 | 169, 159 | 58, 160 | 185, 161 | 247, 162 | 69, 163 | 87, 164 | 175, 165 | 61, 166 | 30, 167 | 24, 168 | 13, 169 | 132, 170 | 76, 171 | 219, 172 | 211, 173 | 187, 174 | 70, 175 | 244, 176 | 141, 177 | 214, 178 | 35, 179 | 173, 180 | 8, 181 | 84, 182 | 175, 183 | 121, 184 | 103, 185 | 254, 186 | 242 187 | ], 188 | "next_validators_hash": [ 189 | 18, 190 | 158, 191 | 139, 192 | 169, 193 | 58, 194 | 185, 195 | 247, 196 | 69, 197 | 87, 198 | 175, 199 | 61, 200 | 30, 201 | 24, 202 | 13, 203 | 132, 204 | 76, 205 | 219, 206 | 211, 207 | 187, 208 | 70, 209 | 244, 210 | 141, 211 | 214, 212 | 35, 213 | 173, 214 | 8, 215 | 84, 216 | 175, 217 | 121, 218 | 103, 219 | 254, 220 | 242 221 | ], 222 | "consensus_hash": [ 223 | 4, 224 | 128, 225 | 145, 226 | 188, 227 | 125, 228 | 220, 229 | 40, 230 | 63, 231 | 119, 232 | 191, 233 | 191, 234 | 145, 235 | 215, 236 | 60, 237 | 68, 238 | 218, 239 | 88, 240 | 195, 241 | 223, 242 | 138, 243 | 156, 244 | 188, 245 | 134, 246 | 116, 247 | 5, 248 | 216, 249 | 183, 250 | 243, 251 | 218, 252 | 173, 253 | 162, 254 | 47 255 | ], 256 | "app_hash": [ 257 | 109, 258 | 255, 259 | 139, 260 | 53, 261 | 120, 262 | 97, 263 | 97, 264 | 249, 265 | 250, 266 | 55, 267 | 52, 268 | 177, 269 | 138, 270 | 86, 271 | 148, 272 | 206, 273 | 210, 274 | 233, 275 | 35, 276 | 141, 277 | 39, 278 | 240, 279 | 56, 280 | 141, 281 | 82, 282 | 76, 283 | 70, 284 | 100, 285 | 172, 286 | 63, 287 | 160, 288 | 135 289 | ], 290 | "last_results_hash": [ 291 | 227, 292 | 176, 293 | 196, 294 | 66, 295 | 152, 296 | 252, 297 | 28, 298 | 20, 299 | 154, 300 | 251, 301 | 244, 302 | 200, 303 | 153, 304 | 111, 305 | 185, 306 | 36, 307 | 39, 308 | 174, 309 | 65, 310 | 228, 311 | 100, 312 | 155, 313 | 147, 314 | 76, 315 | 164, 316 | 149, 317 | 153, 318 | 27, 319 | 120, 320 | 82, 321 | 184, 322 | 85 323 | ], 324 | "evidence_hash": [ 325 | 227, 326 | 176, 327 | 196, 328 | 66, 329 | 152, 330 | 252, 331 | 28, 332 | 20, 333 | 154, 334 | 251, 335 | 244, 336 | 200, 337 | 153, 338 | 111, 339 | 185, 340 | 36, 341 | 39, 342 | 174, 343 | 65, 344 | 228, 345 | 100, 346 | 155, 347 | 147, 348 | 76, 349 | 164, 350 | 149, 351 | 153, 352 | 27, 353 | 120, 354 | 82, 355 | 184, 356 | 85 357 | ], 358 | "proposer_address": [ 359 | 86, 360 | 228, 361 | 152, 362 | 158, 363 | 92, 364 | 202, 365 | 108, 366 | 77, 367 | 59, 368 | 28, 369 | 213, 370 | 105, 371 | 128, 372 | 241, 373 | 174, 374 | 87, 375 | 41, 376 | 69, 377 | 60, 378 | 117 379 | ] 380 | }, 381 | "commit": { 382 | "height": 29, 383 | "round": 0, 384 | "block_id": { 385 | "hash": [ 386 | 190, 387 | 85, 388 | 83, 389 | 90, 390 | 191, 391 | 215, 392 | 223, 393 | 84, 394 | 218, 395 | 96, 396 | 95, 397 | 226, 398 | 134, 399 | 13, 400 | 65, 401 | 104, 402 | 200, 403 | 17, 404 | 250, 405 | 106, 406 | 77, 407 | 221, 408 | 211, 409 | 50, 410 | 75, 411 | 35, 412 | 84, 413 | 230, 414 | 91, 415 | 110, 416 | 253, 417 | 19 418 | ], 419 | "part_set_header": { 420 | "total": 1, 421 | "hash": [ 422 | 1, 423 | 42, 424 | 76, 425 | 160, 426 | 95, 427 | 60, 428 | 102, 429 | 16, 430 | 163, 431 | 1, 432 | 205, 433 | 20, 434 | 139, 435 | 239, 436 | 114, 437 | 24, 438 | 245, 439 | 190, 440 | 158, 441 | 238, 442 | 75, 443 | 100, 444 | 67, 445 | 128, 446 | 254, 447 | 205, 448 | 93, 449 | 231, 450 | 13, 451 | 95, 452 | 73, 453 | 90 454 | ] 455 | } 456 | }, 457 | "signatures": [ 458 | { 459 | "block_id_flag": 2, 460 | "validator_address": [ 461 | 86, 462 | 228, 463 | 152, 464 | 158, 465 | 92, 466 | 202, 467 | 108, 468 | 77, 469 | 59, 470 | 28, 471 | 213, 472 | 105, 473 | 128, 474 | 241, 475 | 174, 476 | 87, 477 | 41, 478 | 69, 479 | 60, 480 | 117 481 | ], 482 | "timestamp": { 483 | "seconds": 1634765012, 484 | "nanos": 545035672 485 | }, 486 | "signature": [ 487 | 241, 488 | 124, 489 | 141, 490 | 55, 491 | 241, 492 | 249, 493 | 94, 494 | 198, 495 | 228, 496 | 54, 497 | 6, 498 | 59, 499 | 154, 500 | 147, 501 | 164, 502 | 134, 503 | 253, 504 | 131, 505 | 71, 506 | 243, 507 | 112, 508 | 18, 509 | 50, 510 | 196, 511 | 180, 512 | 164, 513 | 186, 514 | 76, 515 | 205, 516 | 183, 517 | 68, 518 | 254, 519 | 101, 520 | 195, 521 | 170, 522 | 115, 523 | 35, 524 | 25, 525 | 192, 526 | 90, 527 | 24, 528 | 172, 529 | 66, 530 | 120, 531 | 230, 532 | 124, 533 | 45, 534 | 233, 535 | 3, 536 | 223, 537 | 252, 538 | 97, 539 | 131, 540 | 83, 541 | 147, 542 | 204, 543 | 139, 544 | 165, 545 | 110, 546 | 225, 547 | 186, 548 | 52, 549 | 99, 550 | 8 551 | ] 552 | } 553 | ] 554 | } 555 | } -------------------------------------------------------------------------------- /test/data/header.29.validator_set.json: -------------------------------------------------------------------------------- 1 | { 2 | "validators": [ 3 | { 4 | "address": [ 5 | 86, 6 | 228, 7 | 152, 8 | 158, 9 | 92, 10 | 202, 11 | 108, 12 | 77, 13 | 59, 14 | 28, 15 | 213, 16 | 105, 17 | 128, 18 | 241, 19 | 174, 20 | 87, 21 | 41, 22 | 69, 23 | 60, 24 | 117 25 | ], 26 | "pub_key": { 27 | "ed25519": [ 28 | 119, 29 | 198, 30 | 146, 31 | 235, 32 | 33, 33 | 246, 34 | 5, 35 | 2, 36 | 212, 37 | 34, 38 | 21, 39 | 243, 40 | 254, 41 | 119, 42 | 90, 43 | 111, 44 | 118, 45 | 160, 46 | 155, 47 | 161, 48 | 114, 49 | 56, 50 | 69, 51 | 34, 52 | 19, 53 | 77, 54 | 229, 55 | 243, 56 | 106, 57 | 160, 58 | 4, 59 | 208 60 | ] 61 | }, 62 | "voting_power": 100000, 63 | "proposer_priority": 0 64 | } 65 | ], 66 | "proposer": null, 67 | "total_voting_power": 0 68 | } 69 | -------------------------------------------------------------------------------- /test/data/header.30.signed_header.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "version": { 4 | "block": 11, 5 | "app": 0 6 | }, 7 | "chain_id": "wormhole", 8 | "height": 30, 9 | "time": { 10 | "seconds": 1634765012, 11 | "nanos": 545035672 12 | }, 13 | "last_block_id": { 14 | "hash": [ 15 | 190, 16 | 85, 17 | 83, 18 | 90, 19 | 191, 20 | 215, 21 | 223, 22 | 84, 23 | 218, 24 | 96, 25 | 95, 26 | 226, 27 | 134, 28 | 13, 29 | 65, 30 | 104, 31 | 200, 32 | 17, 33 | 250, 34 | 106, 35 | 77, 36 | 221, 37 | 211, 38 | 50, 39 | 75, 40 | 35, 41 | 84, 42 | 230, 43 | 91, 44 | 110, 45 | 253, 46 | 19 47 | ], 48 | "part_set_header": { 49 | "total": 1, 50 | "hash": [ 51 | 1, 52 | 42, 53 | 76, 54 | 160, 55 | 95, 56 | 60, 57 | 102, 58 | 16, 59 | 163, 60 | 1, 61 | 205, 62 | 20, 63 | 139, 64 | 239, 65 | 114, 66 | 24, 67 | 245, 68 | 190, 69 | 158, 70 | 238, 71 | 75, 72 | 100, 73 | 67, 74 | 128, 75 | 254, 76 | 205, 77 | 93, 78 | 231, 79 | 13, 80 | 95, 81 | 73, 82 | 90 83 | ] 84 | } 85 | }, 86 | "last_commit_hash": [ 87 | 165, 88 | 36, 89 | 254, 90 | 131, 91 | 127, 92 | 117, 93 | 142, 94 | 20, 95 | 233, 96 | 177, 97 | 231, 98 | 137, 99 | 172, 100 | 105, 101 | 90, 102 | 34, 103 | 15, 104 | 180, 105 | 61, 106 | 23, 107 | 198, 108 | 112, 109 | 132, 110 | 97, 111 | 191, 112 | 120, 113 | 252, 114 | 255, 115 | 171, 116 | 130, 117 | 99, 118 | 222 119 | ], 120 | "data_hash": [ 121 | 227, 122 | 176, 123 | 196, 124 | 66, 125 | 152, 126 | 252, 127 | 28, 128 | 20, 129 | 154, 130 | 251, 131 | 244, 132 | 200, 133 | 153, 134 | 111, 135 | 185, 136 | 36, 137 | 39, 138 | 174, 139 | 65, 140 | 228, 141 | 100, 142 | 155, 143 | 147, 144 | 76, 145 | 164, 146 | 149, 147 | 153, 148 | 27, 149 | 120, 150 | 82, 151 | 184, 152 | 85 153 | ], 154 | "validators_hash": [ 155 | 18, 156 | 158, 157 | 139, 158 | 169, 159 | 58, 160 | 185, 161 | 247, 162 | 69, 163 | 87, 164 | 175, 165 | 61, 166 | 30, 167 | 24, 168 | 13, 169 | 132, 170 | 76, 171 | 219, 172 | 211, 173 | 187, 174 | 70, 175 | 244, 176 | 141, 177 | 214, 178 | 35, 179 | 173, 180 | 8, 181 | 84, 182 | 175, 183 | 121, 184 | 103, 185 | 254, 186 | 242 187 | ], 188 | "next_validators_hash": [ 189 | 18, 190 | 158, 191 | 139, 192 | 169, 193 | 58, 194 | 185, 195 | 247, 196 | 69, 197 | 87, 198 | 175, 199 | 61, 200 | 30, 201 | 24, 202 | 13, 203 | 132, 204 | 76, 205 | 219, 206 | 211, 207 | 187, 208 | 70, 209 | 244, 210 | 141, 211 | 214, 212 | 35, 213 | 173, 214 | 8, 215 | 84, 216 | 175, 217 | 121, 218 | 103, 219 | 254, 220 | 242 221 | ], 222 | "consensus_hash": [ 223 | 4, 224 | 128, 225 | 145, 226 | 188, 227 | 125, 228 | 220, 229 | 40, 230 | 63, 231 | 119, 232 | 191, 233 | 191, 234 | 145, 235 | 215, 236 | 60, 237 | 68, 238 | 218, 239 | 88, 240 | 195, 241 | 223, 242 | 138, 243 | 156, 244 | 188, 245 | 134, 246 | 116, 247 | 5, 248 | 216, 249 | 183, 250 | 243, 251 | 218, 252 | 173, 253 | 162, 254 | 47 255 | ], 256 | "app_hash": [ 257 | 107, 258 | 249, 259 | 69, 260 | 177, 261 | 244, 262 | 162, 263 | 70, 264 | 111, 265 | 165, 266 | 55, 267 | 171, 268 | 173, 269 | 27, 270 | 26, 271 | 49, 272 | 196, 273 | 184, 274 | 12, 275 | 109, 276 | 111, 277 | 31, 278 | 110, 279 | 176, 280 | 63, 281 | 212, 282 | 86, 283 | 169, 284 | 234, 285 | 163, 286 | 58, 287 | 214, 288 | 49 289 | ], 290 | "last_results_hash": [ 291 | 227, 292 | 176, 293 | 196, 294 | 66, 295 | 152, 296 | 252, 297 | 28, 298 | 20, 299 | 154, 300 | 251, 301 | 244, 302 | 200, 303 | 153, 304 | 111, 305 | 185, 306 | 36, 307 | 39, 308 | 174, 309 | 65, 310 | 228, 311 | 100, 312 | 155, 313 | 147, 314 | 76, 315 | 164, 316 | 149, 317 | 153, 318 | 27, 319 | 120, 320 | 82, 321 | 184, 322 | 85 323 | ], 324 | "evidence_hash": [ 325 | 227, 326 | 176, 327 | 196, 328 | 66, 329 | 152, 330 | 252, 331 | 28, 332 | 20, 333 | 154, 334 | 251, 335 | 244, 336 | 200, 337 | 153, 338 | 111, 339 | 185, 340 | 36, 341 | 39, 342 | 174, 343 | 65, 344 | 228, 345 | 100, 346 | 155, 347 | 147, 348 | 76, 349 | 164, 350 | 149, 351 | 153, 352 | 27, 353 | 120, 354 | 82, 355 | 184, 356 | 85 357 | ], 358 | "proposer_address": [ 359 | 86, 360 | 228, 361 | 152, 362 | 158, 363 | 92, 364 | 202, 365 | 108, 366 | 77, 367 | 59, 368 | 28, 369 | 213, 370 | 105, 371 | 128, 372 | 241, 373 | 174, 374 | 87, 375 | 41, 376 | 69, 377 | 60, 378 | 117 379 | ] 380 | }, 381 | "commit": { 382 | "height": 30, 383 | "round": 0, 384 | "block_id": { 385 | "hash": [ 386 | 227, 387 | 132, 388 | 23, 389 | 251, 390 | 32, 391 | 139, 392 | 181, 393 | 162, 394 | 175, 395 | 31, 396 | 158, 397 | 85, 398 | 244, 399 | 164, 400 | 23, 401 | 156, 402 | 64, 403 | 227, 404 | 105, 405 | 146, 406 | 118, 407 | 195, 408 | 102, 409 | 217, 410 | 50, 411 | 207, 412 | 105, 413 | 53, 414 | 111, 415 | 51, 416 | 7, 417 | 101 418 | ], 419 | "part_set_header": { 420 | "total": 1, 421 | "hash": [ 422 | 230, 423 | 195, 424 | 184, 425 | 40, 426 | 135, 427 | 127, 428 | 167, 429 | 118, 430 | 89, 431 | 189, 432 | 59, 433 | 85, 434 | 75, 435 | 121, 436 | 213, 437 | 206, 438 | 163, 439 | 191, 440 | 27, 441 | 228, 442 | 255, 443 | 161, 444 | 112, 445 | 34, 446 | 71, 447 | 233, 448 | 152, 449 | 64, 450 | 184, 451 | 102, 452 | 211, 453 | 237 454 | ] 455 | } 456 | }, 457 | "signatures": [ 458 | { 459 | "block_id_flag": 2, 460 | "validator_address": [ 461 | 86, 462 | 228, 463 | 152, 464 | 158, 465 | 92, 466 | 202, 467 | 108, 468 | 77, 469 | 59, 470 | 28, 471 | 213, 472 | 105, 473 | 128, 474 | 241, 475 | 174, 476 | 87, 477 | 41, 478 | 69, 479 | 60, 480 | 117 481 | ], 482 | "timestamp": { 483 | "seconds": 1634765017, 484 | "nanos": 593928365 485 | }, 486 | "signature": [ 487 | 44, 488 | 78, 489 | 130, 490 | 202, 491 | 56, 492 | 137, 493 | 32, 494 | 50, 495 | 84, 496 | 69, 497 | 9, 498 | 222, 499 | 154, 500 | 43, 501 | 187, 502 | 222, 503 | 155, 504 | 235, 505 | 225, 506 | 87, 507 | 217, 508 | 235, 509 | 83, 510 | 7, 511 | 166, 512 | 31, 513 | 112, 514 | 239, 515 | 38, 516 | 12, 517 | 120, 518 | 232, 519 | 43, 520 | 223, 521 | 140, 522 | 78, 523 | 58, 524 | 230, 525 | 1, 526 | 90, 527 | 241, 528 | 17, 529 | 156, 530 | 38, 531 | 204, 532 | 58, 533 | 94, 534 | 229, 535 | 4, 536 | 200, 537 | 61, 538 | 255, 539 | 191, 540 | 169, 541 | 117, 542 | 209, 543 | 242, 544 | 78, 545 | 179, 546 | 175, 547 | 200, 548 | 218, 549 | 99, 550 | 10 551 | ] 552 | } 553 | ] 554 | } 555 | } -------------------------------------------------------------------------------- /test/data/header.30.validator_set.json: -------------------------------------------------------------------------------- 1 | { 2 | "validators": [ 3 | { 4 | "address": [ 5 | 86, 6 | 228, 7 | 152, 8 | 158, 9 | 92, 10 | 202, 11 | 108, 12 | 77, 13 | 59, 14 | 28, 15 | 213, 16 | 105, 17 | 128, 18 | 241, 19 | 174, 20 | 87, 21 | 41, 22 | 69, 23 | 60, 24 | 117 25 | ], 26 | "pub_key": { 27 | "ed25519": [ 28 | 119, 29 | 198, 30 | 146, 31 | 235, 32 | 33, 33 | 246, 34 | 5, 35 | 2, 36 | 212, 37 | 34, 38 | 21, 39 | 243, 40 | 254, 41 | 119, 42 | 90, 43 | 111, 44 | 118, 45 | 160, 46 | 155, 47 | 161, 48 | 114, 49 | 56, 50 | 69, 51 | 34, 52 | 19, 53 | 77, 54 | 229, 55 | 243, 56 | 106, 57 | 160, 58 | 4, 59 | 208 60 | ] 61 | }, 62 | "voting_power": 100000, 63 | "proposer_priority": 0 64 | } 65 | ], 66 | "proposer": null, 67 | "total_voting_power": 0 68 | } 69 | -------------------------------------------------------------------------------- /test/demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | web3 = "0.17.0" 10 | tokio = "1" 11 | hex = "0.4.3" 12 | serde_json = "1.0" 13 | serde = "1.0" 14 | ethabi = "14.1.0" 15 | tonic = "0.4.0" 16 | prost = "0.9.0" 17 | prost-types = "0.9.0" 18 | tendermint-rpc = { version = "0.23", features = ["websocket-client", "http-client"], default-features = false } 19 | tendermint = { version = "0.23", default-features = false } 20 | ibc = "0.7.0" 21 | futures = "0.3.5" 22 | tendermint-proto = "0.23" 23 | prost-helper = "0.2.0" 24 | clap = "2.33.3" 25 | secp256k1 = { version = "0.20.3", features = ["rand-std"] } 26 | 27 | [build-dependencies] 28 | tonic-build = "0.4.0" 29 | prost-serde = "0.2.0" 30 | serde_json = "1.0" 31 | serde = "1.0" 32 | -------------------------------------------------------------------------------- /test/demo/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tonic_build::configure() 3 | .type_attribute(".", "#[derive(::serde::Serialize, ::serde::Deserialize)]") 4 | .build_server(false) 5 | .build_client(true) 6 | .compile( 7 | &["../../proto/TendermintLight.proto"], 8 | &["../../proto", "third_party/proto/"], 9 | ) 10 | .unwrap(); 11 | } 12 | -------------------------------------------------------------------------------- /test/demo/src/abci.rs: -------------------------------------------------------------------------------- 1 | /// Perform a generic `abci_query`, and return the corresponding deserialized response data. 2 | async fn abci_query( 3 | chain: &CosmosSdkChain, 4 | path: TendermintABCIPath, 5 | data: String, 6 | height: Height, 7 | prove: bool, 8 | ) -> Result { 9 | let height = if height.value() == 0 { 10 | None 11 | } else { 12 | Some(height) 13 | }; 14 | 15 | // Use the Tendermint-rs RPC client to do the query. 16 | let response = chain 17 | .rpc_client() 18 | .abci_query(Some(path), data.into_bytes(), height, prove) 19 | .await 20 | .map_err(|e| Error::rpc(chain.config.rpc_addr.clone(), e))?; 21 | 22 | if !response.code.is_ok() { 23 | // Fail with response log. 24 | return Err(Error::abci_query(response)); 25 | } 26 | 27 | if prove && response.proof.is_none() { 28 | // Fail due to empty proof 29 | return Err(Error::empty_response_proof()); 30 | } 31 | 32 | let proof = response 33 | .proof 34 | .map(|p| convert_tm_to_ics_merkle_proof(&p)) 35 | .transpose() 36 | .map_err(Error::ics23)?; 37 | 38 | let response = QueryResponse { 39 | value: response.value, 40 | height: response.height, 41 | proof, 42 | }; 43 | 44 | Ok(response) 45 | } 46 | 47 | fn query(&self, data: Path, height: ICSHeight, prove: bool) -> Result { 48 | crate::time!("query"); 49 | 50 | let path = TendermintABCIPath::from_str(IBC_QUERY_PATH).unwrap(); 51 | 52 | let height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?; 53 | 54 | if !data.is_provable() & prove { 55 | return Err(Error::private_store()); 56 | } 57 | 58 | let response = self.block_on(abci_query(self, path, data.to_string(), height, prove))?; 59 | 60 | // TODO - Verify response proof, if requested. 61 | if prove {} 62 | 63 | Ok(response) 64 | } 65 | -------------------------------------------------------------------------------- /test/demo/src/consts.rs: -------------------------------------------------------------------------------- 1 | pub const IBC_HOST_ADDRESS: &str = "0xFEAB95Eeb8507978bC5edD22E9BA2F52f9d377A1"; 2 | pub const IBC_HANDLER_ADDRESS: &str = "0x6fdA347f2A64fd55F43B63c28619548c9B362835"; 3 | pub const IBC_IDENTIFIER_ADDRESS: &str = "0x173290F876E8DBe940a5579dA25aa03F36df977E"; 4 | pub const TENDERMINT_LIGHT_CLIENT_ADDRESS: &str = "0xff67836F5cb28030F6B8bDC32736F69e2e91d3F2"; 5 | -------------------------------------------------------------------------------- /test/demo/src/eth.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::{error::Error, fs::File, io::BufReader, path::Path}; 3 | use web3::{contract::Contract, types::H160}; 4 | 5 | #[derive(Deserialize, Serialize, Debug)] 6 | pub struct ABI { 7 | abi: Vec, 8 | } 9 | 10 | pub fn read_abi_from_file>(path: P) -> Result> { 11 | let file = File::open(path)?; 12 | let reader = BufReader::new(file); 13 | 14 | Ok(serde_json::from_reader(reader)?) 15 | } 16 | 17 | pub fn load_contract<'a, T: web3::Transport>( 18 | transport: &'a T, 19 | abipath: &'static str, 20 | address: &'static str, 21 | ) -> Result, Box> { 22 | let web3 = web3::Web3::new(transport); 23 | let abi = read_abi_from_file(abipath)?; 24 | let serialized_abi: Vec = serde_json::to_vec(&abi.abi)?; 25 | 26 | let raw: Vec = hex::decode(&address[2..address.len()])?; 27 | let mut addr: [u8; 20] = Default::default(); 28 | addr.copy_from_slice(&raw[0..20]); 29 | 30 | Ok(Contract::from_json(web3.eth(), H160::from(&addr), &serialized_abi)?) 31 | } 32 | 33 | pub async fn get_client_ids<'a, T: web3::Transport>( 34 | transport: &'a T, 35 | contract: &Contract<&'a T>, 36 | ) -> Result, web3::contract::Error> { 37 | let res = contract 38 | .abi() 39 | .event("GeneratedClientIdentifier") 40 | .and_then(|ev| { 41 | let filter = ev.filter(ethabi::RawTopicFilter { 42 | topic0: ethabi::Topic::Any, 43 | topic1: ethabi::Topic::Any, 44 | topic2: ethabi::Topic::Any, 45 | })?; 46 | Ok((ev.clone(), filter)) 47 | }); 48 | 49 | let (ev, filter) = match res { 50 | Ok(x) => x, 51 | Err(e) => return Err(e.into()), 52 | }; 53 | 54 | let logs = web3::Web3::new(transport) 55 | .eth() 56 | .logs( 57 | web3::types::FilterBuilder::default() 58 | .address(vec![contract.address()]) 59 | .from_block(web3::types::BlockNumber::from(0)) 60 | .topic_filter(filter) 61 | .build(), 62 | ) 63 | .await?; 64 | 65 | logs.into_iter() 66 | .map(move |l| { 67 | let log = ev.parse_log(ethabi::RawLog { 68 | topics: l.topics, 69 | data: l.data.0, 70 | })?; 71 | 72 | Ok( 73 | log.params.into_iter().map(|x| x.value).collect::>()[0] 74 | .clone() 75 | .to_string(), 76 | ) 77 | }) 78 | .collect::, web3::contract::Error>>() 79 | } 80 | -------------------------------------------------------------------------------- /test/demo/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod consts; 2 | -------------------------------------------------------------------------------- /test/demo/src/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod consts; 2 | -------------------------------------------------------------------------------- /test/demo/src/proto.rs: -------------------------------------------------------------------------------- 1 | use prost::Message; 2 | use prost_types::Any; 3 | 4 | pub mod tendermint { 5 | pub mod light { 6 | tonic::include_proto!("tendermint.light"); 7 | } 8 | } 9 | 10 | pub fn prost_serialize(msg: &T) -> Result, prost::EncodeError> { 11 | let mut buf = Vec::new(); 12 | msg.encode(&mut buf)?; 13 | 14 | Ok(buf) 15 | } 16 | 17 | pub fn prost_serialize_any( 18 | msg: &T, 19 | type_url: &'static str, 20 | ) -> Result, prost::EncodeError> { 21 | let any = Any { 22 | type_url: type_url.to_string(), 23 | value: prost_serialize(msg)?, 24 | }; 25 | 26 | let serialized = prost_serialize(&any)?; 27 | 28 | Ok(serialized) 29 | } 30 | -------------------------------------------------------------------------------- /test/demo/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use web3::types::H160; 3 | 4 | use crate::proto::tendermint::light::{ 5 | BlockId, BlockIdFlag, CommitSig, Consensus, Duration, PartSetHeader, PublicKey, SignedHeader, 6 | Timestamp, TmHeader, Validator, ValidatorSet, 7 | }; 8 | 9 | pub fn to_part_set_header(part_set_header: &tendermint::block::parts::Header) -> PartSetHeader { 10 | PartSetHeader { 11 | total: part_set_header.total, 12 | hash: part_set_header.hash.as_bytes().to_vec(), 13 | } 14 | } 15 | 16 | pub fn to_block_id(last_block_id: &tendermint::block::Id) -> BlockId { 17 | BlockId { 18 | hash: last_block_id.hash.as_bytes().to_vec(), 19 | part_set_header: Some(to_part_set_header(&last_block_id.part_set_header)), 20 | } 21 | } 22 | 23 | pub fn to_timestamp(timestamp: &tendermint::time::Time) -> Timestamp { 24 | let nanos = timestamp.0.timestamp_subsec_nanos().try_into().unwrap_or(0); 25 | let seconds = timestamp.0.timestamp(); 26 | Timestamp { 27 | seconds: seconds, 28 | nanos: nanos, 29 | } 30 | } 31 | 32 | pub fn to_version(version: &tendermint::block::header::Version) -> Consensus { 33 | Consensus { 34 | block: version.block, 35 | app: version.app, 36 | } 37 | } 38 | 39 | pub fn to_sig(sig: &tendermint::block::commit_sig::CommitSig) -> CommitSig { 40 | match sig { 41 | tendermint::block::commit_sig::CommitSig::BlockIdFlagAbsent => CommitSig { 42 | block_id_flag: BlockIdFlag::Absent.into(), 43 | validator_address: Vec::new(), 44 | timestamp: None, 45 | signature: Vec::new(), 46 | }, 47 | tendermint::block::commit_sig::CommitSig::BlockIdFlagNil { 48 | validator_address, 49 | timestamp, 50 | signature, 51 | } => CommitSig { 52 | block_id_flag: BlockIdFlag::Nil.into(), 53 | validator_address: validator_address.to_owned().into(), 54 | timestamp: Some(to_timestamp(×tamp)), 55 | signature: signature.to_owned().unwrap().into(), 56 | }, 57 | tendermint::block::commit_sig::CommitSig::BlockIdFlagCommit { 58 | validator_address, 59 | timestamp, 60 | signature, 61 | } => CommitSig { 62 | block_id_flag: BlockIdFlag::Commit.into(), 63 | validator_address: validator_address.to_owned().into(), 64 | timestamp: Some(to_timestamp(×tamp)), 65 | signature: signature.to_owned().unwrap().into(), 66 | }, 67 | } 68 | } 69 | 70 | pub fn to_signed_header( 71 | signed_header: &tendermint::block::signed_header::SignedHeader, 72 | ) -> SignedHeader { 73 | let header = &signed_header.header; 74 | let commit = &signed_header.commit; 75 | 76 | SignedHeader { 77 | header: Some(crate::proto::tendermint::light::LightHeader { 78 | chain_id: header.chain_id.to_string(), 79 | time: Some(to_timestamp(&header.time)), 80 | height: header.height.into(), 81 | next_validators_hash: header.next_validators_hash.into(), 82 | validators_hash: header.validators_hash.into(), 83 | app_hash: header.app_hash.to_owned().into(), 84 | consensus_hash: header.consensus_hash.into(), 85 | data_hash: header.data_hash.unwrap().into(), 86 | evidence_hash: header.evidence_hash.unwrap().into(), 87 | last_block_id: Some(to_block_id(&header.last_block_id.unwrap())), 88 | last_commit_hash: header.last_commit_hash.unwrap().into(), 89 | last_results_hash: header.last_results_hash.unwrap().into(), 90 | proposer_address: header.proposer_address.into(), 91 | version: Some(to_version(&header.version)), 92 | }), 93 | commit: Some(crate::proto::tendermint::light::Commit { 94 | height: commit.height.into(), 95 | round: commit.round.into(), 96 | block_id: Some(to_block_id(&commit.block_id)), 97 | signatures: commit.signatures.iter().map(|sig| to_sig(sig)).collect(), 98 | }), 99 | } 100 | } 101 | 102 | pub fn to_validator_set(validators: &[tendermint::validator::Info]) -> ValidatorSet { 103 | ValidatorSet { 104 | validators: validators 105 | .iter() 106 | .map(|validator| Validator { 107 | address: validator.address.into(), 108 | pub_key: Some(PublicKey { 109 | sum: Some(crate::proto::tendermint::light::public_key::Sum::Ed25519( 110 | validator.pub_key.to_bytes().to_vec(), 111 | )), 112 | }), 113 | voting_power: validator.power.into(), 114 | proposer_priority: validator.proposer_priority.into(), 115 | }) 116 | .collect(), 117 | proposer: None, 118 | total_voting_power: 0, 119 | } 120 | } 121 | 122 | pub fn to_light_block(signed_header: &SignedHeader, validator_set: &ValidatorSet) -> TmHeader { 123 | TmHeader { 124 | trusted_validators: None, 125 | trusted_height: 0, 126 | signed_header: Some(signed_header.to_owned()), 127 | validator_set: Some(validator_set.to_owned()), 128 | } 129 | } 130 | 131 | pub fn to_duration(seconds: i64, nanos: i32) -> Duration { 132 | Duration { seconds, nanos } 133 | } 134 | 135 | pub fn to_addr(address: String) -> H160 { 136 | let stripped: Vec = hex::decode(&address[2..address.len()]).unwrap(); 137 | let mut addr: [u8; 20] = Default::default(); 138 | addr.copy_from_slice(&stripped[0..20]); 139 | 140 | H160::from(&addr) 141 | } 142 | -------------------------------------------------------------------------------- /test/demo/src/util.rs: -------------------------------------------------------------------------------- 1 | use secp256k1::{key::SecretKey, rand::rngs::OsRng, Secp256k1}; 2 | use std::{ 3 | error::Error, 4 | fs::File, 5 | io::{Read, Write}, 6 | path::Path, 7 | }; 8 | 9 | pub fn get_celo_private_key(celo_private_key_path: &str) -> Result> { 10 | if Path::new(celo_private_key_path).exists() { 11 | let mut secret = String::new(); 12 | let mut ifile = File::open(celo_private_key_path)?; 13 | ifile.read_to_string(&mut secret)?; 14 | 15 | Ok(SecretKey::from_slice(&hex::decode( 16 | secret.strip_prefix("0x").unwrap(), 17 | )?)?) 18 | } else { 19 | let secp = Secp256k1::new(); 20 | let mut rng = OsRng::new().expect("OsRng"); 21 | let (secret_key, _public_key) = secp.generate_keypair(&mut rng); 22 | 23 | let mut ifile = File::create(celo_private_key_path)?; 24 | ifile.write_all(format!("0x{}", hex::encode(secret_key.as_ref())).as_bytes())?; 25 | 26 | println!( 27 | "[0] generated a new celo private key: {}", 28 | celo_private_key_path 29 | ); 30 | 31 | Ok(secret_key) 32 | } 33 | } 34 | 35 | pub async fn calculate_and_display_fee<'a, T: web3::Transport>( 36 | prefix: &'a str, 37 | client_id: String, 38 | transport: &T, 39 | reciept: &web3::types::TransactionReceipt, 40 | celo_usd_price: f64, 41 | celo_gas_price: f64, 42 | ) { 43 | let web3 = web3::Web3::new(&transport); 44 | let tx: web3::types::Transaction = web3 45 | .eth() 46 | .transaction(web3::types::TransactionId::Hash(reciept.transaction_hash)) 47 | .await 48 | .unwrap() 49 | .unwrap(); 50 | 51 | let gas_price = if tx.gas_price.as_u64() == 0 { 52 | celo_gas_price 53 | } else { 54 | tx.gas_price.as_u64() as f64 55 | }; // wei 56 | let gas_used = reciept.gas_used.unwrap().as_u64(); 57 | let fee = (gas_price * gas_used as f64) / 1e18; 58 | let fee_usd = celo_usd_price * fee; 59 | 60 | println!( 61 | "{}[{}] gas: {}, gas_used: {}; gas_price: {}; fee(CELO): {}; fee(USD): {}", 62 | prefix, client_id, tx.gas, gas_used, gas_price, fee, fee_usd 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /test/lib.js: -------------------------------------------------------------------------------- 1 | const protobuf = require('protobufjs') 2 | 3 | async function call (fn, errMsg) { 4 | try { 5 | const tx = await fn() 6 | console.log(tx) 7 | } catch (error) { 8 | console.log(errMsg) 9 | const tx = await web3.eth.getTransaction(error.tx) 10 | await web3.eth.call(tx) 11 | } 12 | } 13 | 14 | async function readHeader (height) { 15 | const root = new protobuf.Root() 16 | let vs, sh 17 | 18 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 19 | if (err) { throw err } 20 | 21 | // types 22 | const ValidatorSet = root.lookupType('tendermint.light.ValidatorSet') 23 | const SignedHeader = root.lookupType('tendermint.light.SignedHeader') 24 | 25 | // core structs 26 | const validatorSetObj = require('./data/header.' + height + '.validator_set.json') 27 | vs = ValidatorSet.fromObject(validatorSetObj) 28 | 29 | const headerObj = require('./data/header.' + height + '.signed_header.json') 30 | sh = SignedHeader.fromObject(headerObj) 31 | }) 32 | 33 | return [sh, vs] 34 | } 35 | 36 | function toHexString (byteArray) { 37 | return '0x' + Array.from(byteArray, function (byte) { 38 | return ('0' + (byte & 0xFF).toString(16)).slice(-2) 39 | }).join('') 40 | } 41 | 42 | module.exports = { 43 | call: call, 44 | readHeader: readHeader, 45 | toHexString: toHexString 46 | } 47 | -------------------------------------------------------------------------------- /test/utlis/Tendermint.test.js: -------------------------------------------------------------------------------- 1 | const TendermintMock = artifacts.require('TendermintMock') 2 | const protobuf = require('protobufjs') 3 | const lib = require('../lib.js') 4 | 5 | contract('TendermintMock', () => { 6 | it('verifies signed header hash', async () => { 7 | const mock = await TendermintMock.deployed() 8 | const root = new protobuf.Root() 9 | 10 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 11 | if (err) { throw err } 12 | 13 | const SignedHeader = root.lookupType('tendermint.light.SignedHeader') 14 | 15 | const [sh, vs] = await lib.readHeader(8619996) 16 | const encoded = SignedHeader.encode(sh).finish() 17 | 18 | const expectedHash = '0x' + Buffer.from(sh.commit.block_id.hash).toString('hex') 19 | const hash = await mock.signedHeaderHash.call(encoded) 20 | 21 | assert.equal(hash, expectedHash, 'invalid signed header hash, expected: ' + expectedHash) 22 | }) 23 | }) 24 | 25 | it('verifies total voting power', async () => { 26 | const mock = await TendermintMock.deployed() 27 | const root = new protobuf.Root() 28 | 29 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 30 | if (err) { throw err } 31 | 32 | const ValidatorSet = root.lookupType('tendermint.light.ValidatorSet') 33 | 34 | const [sh, vs] = await lib.readHeader(8619996) 35 | const encoded = await ValidatorSet.encode(vs).finish() 36 | const votingPower = await mock.totalVotingPower.call(encoded) 37 | 38 | assert.equal(votingPower.toNumber(), 169879495, 'invalid voting power') 39 | }) 40 | }) 41 | 42 | it('verifies filtering validator set by address', async () => { 43 | const mock = await TendermintMock.deployed() 44 | const root = new protobuf.Root() 45 | 46 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 47 | if (err) { throw err } 48 | 49 | const ValidatorSet = root.lookupType('tendermint.light.ValidatorSet') 50 | 51 | const [sh, vs] = await lib.readHeader(8619996) 52 | const encoded = await ValidatorSet.encode(vs).finish() 53 | const { 0: index, 1: found } = await mock.getByAddress.call(encoded, vs.validators[0].address) 54 | 55 | assert.equal(index.toNumber(), 0, 'invalid index') 56 | assert.equal(found, true, 'invalid search result') 57 | 58 | const { 0: index, 1: found } = await mock.getByAddress.call(encoded, Buffer.from([0x1, 0x2, 0x3])) 59 | 60 | assert.equal(index.toNumber(), 0, 'invalid index') 61 | assert.equal(found, false, 'invalid search result') 62 | }) 63 | }) 64 | 65 | it('verifies validator set hash', async () => { 66 | const mock = await TendermintMock.deployed() 67 | const root = new protobuf.Root() 68 | 69 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 70 | if (err) { throw err } 71 | 72 | const ValidatorSet = root.lookupType('tendermint.light.ValidatorSet') 73 | 74 | const [sh, vs] = await lib.readHeader(8619996) 75 | const encoded = await ValidatorSet.encode(vs).finish() 76 | const hash = await mock.validatorSetHash.call(encoded) 77 | const expected = lib.toHexString(sh.header.validators_hash) 78 | 79 | assert.equal(expected, hash, 'invalid validator hash') 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /test/utlis/crypto/Ed25519.test.js: -------------------------------------------------------------------------------- 1 | const Ed25519Mock = artifacts.require('Ed25519Mock') 2 | const truffleAssert = require('truffle-assertions') 3 | 4 | contract('Ed25519Mock', () => { 5 | it('verifies ed25519 signature', async () => { 6 | // currently ed25519 verification uses CeloEVM precompile, skip if not 7 | // running on CeloEVM 8 | const nodeInfo = await web3.eth.getNodeInfo() 9 | if (!nodeInfo.startsWith('celo')) { 10 | console.warn('ed25519 signature verification SKIPPED') 11 | return 12 | } 13 | 14 | const mock = await Ed25519Mock.deployed() 15 | 16 | const cases = [ 17 | { 18 | msg: '0x6c080211ae0000000000000022480a201be10f1d98a078e24f7d153d435287f6c62dbbd84a1347e8d1779e6b90d9adc0122408011220be2920b8b9906102d29a0367c69e03d5f0082c49686f5c737fe02bd54ac69c842a0b0890dcb58b0610a883e33b3208776f726d686f6c65', 19 | sig: '0x22aa68771d6c00e322e6341e948f728bfe900423d2faed6f71a3c2e03af256524e213a9eef75f80e50406464d18b1e65c7b95819597123cab5ea8f6f2da8ba08', 20 | expected: true 21 | }, 22 | { 23 | msg: '0x6c080211ae0000000000000022480a201be10f1d98a078e24f7d153d435287f6c62dbbd84a1347e8d1779e6b90d9adc0122408011220be2920b8b9906102d29a0367c69e03d5f0082c49686f5c737fe02bd54ac69c842a0b0890dcb58b0610a883e33b3208776f726d686f6c65', 24 | // invalid sig 25 | sig: '0x22aa68771d6c00e322e6341e948f728bfe900423d2faed6f71a3c2e03af256524e213a9eef75f80e50406464d18b1e65c7b95819597123cab5ea8f6f2da8ba09', 26 | expected: false 27 | } 28 | ] 29 | 30 | // positive case 31 | for (const c of cases) { 32 | const pk = '0x1361bf11753516e1c96d0ec78c59ba44964b4b28bbfe50242b6eced65420f9c8' 33 | const verify = await mock.verify.call(c.msg, pk, c.sig) 34 | 35 | assert.equal(verify, c.expected, 'ed25519 signature verification failed') 36 | } 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/utlis/crypto/MerkleTree.test.js: -------------------------------------------------------------------------------- 1 | const MerkleTreeMock = artifacts.require('MerkleTreeMock') 2 | const protobuf = require('protobufjs') 3 | 4 | // TODO: use mainnet data 5 | contract('MerkleTreeMock', () => { 6 | it('verifies merkle root hash', async () => { 7 | const mock = await MerkleTreeMock.deployed() 8 | const root = new protobuf.Root() 9 | 10 | await root.load('./proto/TendermintLight.proto', { keepCase: true }).then(async function (root, err) { 11 | if (err) { throw err } 12 | 13 | const ValidatorSet = root.lookupType('tendermint.light.ValidatorSet') 14 | const validatorSetObj = require('../../data/header.8619997.validator_set.json') 15 | const headerObj = require('../../data/header.8619997.signed_header.json') 16 | const vs = ValidatorSet.fromObject(validatorSetObj) 17 | 18 | const cases = [ 19 | { 20 | validator_set: vs, 21 | expected: '0x' + Buffer.from(headerObj.header.validators_hash).toString('hex') 22 | }, 23 | { 24 | validator_set: (() => { 25 | const vsObj = validatorSetObj 26 | vsObj.validators = [] 27 | return ValidatorSet.fromObject(vsObj) 28 | })(), 29 | expected: '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' 30 | } 31 | ] 32 | 33 | for (const c of cases) { 34 | const encoded = await ValidatorSet.encode(c.validator_set).finish() 35 | 36 | const hash = await mock.merkleRootHash.call(encoded, 0, c.validator_set.validators.length) 37 | 38 | assert.equal(hash, c.expected, 'invalid merkle root hash, expected: ' + c.expected) 39 | } 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/utlis/crypto/Secp256k1.test.js: -------------------------------------------------------------------------------- 1 | const Secp256k1Mock = artifacts.require('Secp256k1Mock') 2 | const truffleAssert = require('truffle-assertions') 3 | 4 | contract('Secp256k1Mock', () => { 5 | it('serializes compressed public key', async () => { 6 | const mock = await Secp256k1Mock.deployed() 7 | 8 | const cases = [ 9 | { 10 | pk: '0x02fc6c77e870eb482c4d7a0126cf528c3a6775f7fe3f291cbcd1dc0c3b7cc213ae', 11 | expected: '0x04fc6c77e870eb482c4d7a0126cf528c3a6775f7fe3f291cbcd1dc0c3b7cc213ae107a10e61e384f16a3ef1273b55d354b4681774567e9499f21b7eb5f51eb528a' 12 | }, 13 | { 14 | pk: '0x0317088765d17a1232f9757ef7cf992046f383468de5e0e434379de1b8299039ff', 15 | expected: '0x0417088765d17a1232f9757ef7cf992046f383468de5e0e434379de1b8299039ffcf73f4c9a35d4deb22114bdf5f3b6a6b7f5c985f5c21ef3dd68b722f9dd1ed43' 16 | }, 17 | { 18 | pk: '0x0317088765', // too short 19 | expected: '' 20 | }, 21 | { 22 | pk: '0x0517088765d17a1232f9757ef7cf992046f383468de5e0e434379de1b8299039ff', // wrong prefix 23 | expected: '' 24 | } 25 | ] 26 | 27 | for (const c of cases) { 28 | const serialized = mock.serializePubkey.call(c.pk, true) 29 | 30 | if (c.expected.length) { 31 | assert.equal(await serialized, c.expected, 'invalid serialized public key') 32 | } else { 33 | await truffleAssert.reverts(serialized, 'Secp256k1: PK must be compressed') 34 | } 35 | } 36 | }) 37 | 38 | it('verifies secp256k1 signature', async () => { 39 | const mock = await Secp256k1Mock.deployed() 40 | 41 | const cases = [ 42 | { 43 | msg: '0x6c080211140000000000000022480a201b234e2eb7fbd0045d30aff27f378f33bde4a93d7dff48e047e23dd64ea6551d122408011220ec9447ed497eeb1cdbe0121a8c7a449b84ac5d4b8618bb984b3e8206028d4f732a0b08d0f6ad8b0610daa4b3173208776f726d686f6c65', 44 | sig: '0x051412327c5e160d767ef8b081c0e72f329130400b42553f4aa06a4d19397e395371ecd46c59eadc13a92e78fe420bf20bf55d608334981eb13a79ea266d9e28', 45 | v: 28 46 | }, 47 | { 48 | msg: '0x6c080211080000000000000022480a20d892db02a8f682a8ecff369537f7f47672c1b73ae1dbfe234ebace17bfc0c3f412240801122098bf0b3e24565f0ccd182715619192f8df3ad2d447a62f5274bcaa8f3057731d2a0b08a2f2ad8b0610caabcd7c3208776f726d686f6c65', 49 | sig: '0xb149b87d189c08f0ca164a5c0e4a28e6acfd0b745767a673905742ba9682a4a00d346b7a0a8b92f0d779b053c042a3ed0c7b5ca5a1e0b608e1aedeea395841d5', 50 | v: 27 51 | } 52 | ] 53 | 54 | // positive case 55 | for (const c of cases) { 56 | const pk = '0x0317088765d17a1232f9757ef7cf992046f383468de5e0e434379de1b8299039ff' 57 | const verify = await mock.verify.call(c.msg, pk, c.sig) 58 | const recovered = await mock.recover.call(c.msg, c.sig, c.v) 59 | 60 | assert.equal(recovered, '0x083C31D442e15874407c8d9d17D17f26bf53ef34', 'recovered invalid signer address') 61 | assert.equal(verify, true, 'secp256k1 signature verification failed') 62 | } 63 | 64 | // negative case 65 | for (const c of cases) { 66 | const pk = '0x0317088765d17a1232f9757ef7cf992046f383468de5e0e434379de1b8299039fe' 67 | const verify = await mock.verify.call(c.msg, pk, c.sig) 68 | const recovered = await mock.recover.call(c.msg, c.sig, c.v === 27 ? 28 : 27) 69 | 70 | assert.notEqual(recovered, '0x083C31D442e15874407c8d9d17D17f26bf53ef34', 'recovered address worked?') 71 | assert.equal(verify, false, 'secp256k1 verification should fail for wrong pk') 72 | } 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('@truffle/hdwallet-provider') 2 | 3 | // local geth node deps 4 | const mnemonic = 'math razor capable expose worth grape metal sunset metal sudden usage scheme' 5 | 6 | // celo testnet deps 7 | const Web3 = require('web3') 8 | const ContractKit = require('@celo/contractkit') 9 | const web3 = new Web3('https://alfajores-forno.celo-testnet.org') 10 | const kit = ContractKit.newKitFromWeb3(web3) 11 | const getAccount = require('./scripts/getAccount').getAccount 12 | 13 | // get celo testnet account 14 | async function awaitWrapper () { 15 | const account = await getAccount() 16 | kit.connection.addAccount(account.privateKey) 17 | 18 | console.log('Celo account addr: ' + account.address) 19 | } 20 | awaitWrapper() 21 | 22 | // networks configuration 23 | const celo = { 24 | host: '127.0.0.1', 25 | port: 8545, 26 | network_id: '*', 27 | networkCheckTimeout: 100000000, 28 | timeoutBlocks: 200, 29 | websocket: true, 30 | gas: 20000000, 31 | provider: () => 32 | new HDWalletProvider(mnemonic, 'ws://127.0.0.1:3334', 0, 10) 33 | } 34 | 35 | module.exports = { 36 | networks: { 37 | celo: celo, 38 | tests: celo, 39 | ganache: { 40 | host: 'localhost', 41 | port: 8545, 42 | network_id: '*' 43 | }, 44 | testnet: { 45 | gas: 20000000, // current gas limit (as of 2021-12-03) 46 | provider: kit.connection.web3.currentProvider, // CeloProvider 47 | network_id: 44787 // Alfajores network id (testnet) 48 | } 49 | }, 50 | compilers: { 51 | solc: { 52 | version: '0.8.2', 53 | settings: { 54 | optimizer: { 55 | enabled: true, 56 | runs: 2 57 | } 58 | } 59 | } 60 | } 61 | } 62 | --------------------------------------------------------------------------------