NEAR <> Polkadot using IBC Trustless bridging: Requests
1. Pre-compiles/Host functions for signature verification
Introduction
In order to deploy a light client that’s able to verify the state of a chain within
another chain, there’s a need to verify signatures. Signature verification is an
ubiquitous operation, especially in PoS consensus mechanisms.
Our rough numbers say that we’ll be verifying ~200 signatures every minute (Polkadot’s authority set is 300 signers). Therefore, we need a mechanism to perform these operations in a cost-effective way in terms of gas and speed. Currently, NEAR does not have any native signature verification toolbox. This implies that a light client operating insid NEAR will have
to import a library compiled to WASM as mentioned in Zulip.
A first draft of how these pre-compiles interface could look like:
pub fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool
pub fn sr25519_verify(sig: &sr25519::Signature, msg: &[u8], pub_key: &sr25519::Public) -> bool
pub fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool
Pitfalls and benchmarks
The biggest pitfall of the current solution is performance and cost. Our benchamarks show the following results:
verify_ed25519
near call sitoula-test.testnet verify_ed25519 '{"signature_p1": [145,193,203,18,114,227,14,117,33,213,121,66,130,14,25,4,36,120,46,142,226,215,7,66,122,112,97,30,249,135,61,165], "signature_p2": [221,249,252,23,105,40,56,70,31,152,236,141,154,122,207,20,75,118,79,90,168,6,221,122,213,29,126,196,216,104,191,6], "msg": [107,97,106,100,108,102,107,106,97,108,107,102,106,97,107,108,102,106,100,107,108,97,100,106,102,107,108,106,97,100,115,107], "iterations": 10}' --accountId sitoula-test.testnet --gas 300000000000000
# transaction id DZMuFHisupKW42w3giWxTRw5nhBviPu4YZLgKZ6cK4Uq
verify_schnorrkel
near call sitoula-test.testnet verify_schnorrkel '{"signature_p1": [106, 144, 17, 34, 142, 65, 191, 241, 233, 250, 132, 168, 204, 173, 122, 196, 118, 248, 159, 159, 254, 37, 153, 84, 248, 104, 206, 217, 168, 65, 12, 74], "signature_p2": [183, 134, 143, 30, 123, 61, 112, 153, 244, 109, 199, 195, 164, 0, 7, 55, 26, 199, 164, 219, 147, 217, 157, 239, 198, 108, 162, 246, 52, 49, 116, 132], "msg": [107,97,106,100,108,102,107,106,97,108,107,102,106,97,107,108,102,106,100,107,108,97,100,106,102,107,108,106,97,100,115,107], "iterations": 10}' --accountId sitoula-test.testnet --gas 300000000000000
# transaction id DpWpK2jnyBRgZ7fScaBKULikJyvmMjDHx7AyWMrJL5VB
verify_ecdsa
near call sitoula-test.testnet verify_ecdsa '{"signature_p1": [231, 117, 17, 89, 49, 142, 111, 201, 161, 107, 167, 147, 215, 167, 196, 226, 200, 176, 184, 62, 196, 240, 210, 137, 77, 198, 90, 97, 201, 212, 96, 229], "signature_p2": [1, 31, 7, 121, 178, 247, 150, 131, 108, 250, 173, 71, 100, 192, 83, 64, 145, 85, 254, 69, 176, 7, 114, 89, 64, 205, 30, 243, 193, 78, 142, 27], "msg": [107,97,106,100,108,102,107,106,97,108,107,102,106,97,107,108,102,106,100,107,108,97,100,106,102,107,108,106,97,100,115,107], "iterations": 10}' --accountId sitoula-test.testnet --gas 300000000000000
# transaction id AKFwCuQ8g2a8t9SX7vcve348BvwoU3M42MSef7gy1kPA
With iterations = 130
all these calls return ExecutionError: 'Exceeded the maximum amount of gas allowed to burn per contract.'
With iterations = 50
these are the results:
ed25518: tx id 6DcJYfkp9fGxDGtQLZ2m6PEDBwKHXpk7Lf5VgDYLi9vB (299 Tgas)
schnorrkel: tx id ACYJh7YC4pQM8fu7DsjmmkSb3WSvikfQkVyej3htiuDQ (304 Tgas)
ecdsa: tx id 2FhtFEbakuwQyHWcyeRoPkCDgXz4erTYSD75QkxDTb2e (185 Tgas)
Note that these results show that’s currently not possible to validate all 130 signatures in just one transaction, which would be something very desirable. We are hitting gas limits with the wasm libraries for all schemes. The cutoff number seems to be around 50 signatures into one transaction.
Request
We therefore request that these three signatures schemes (ed25519
+ sr25519
+ ecdsa on secp256k1
) are included
within NEAR’s runtime as precompiled. It will allow us to run these operations in a
fast, secure and cost effective way. This will ultimately yield a better user experience
for users willing to unlock the full potential of a composable ecosystem across chains.
2. Request the state of accounts
and contracts
with an attached merkle proof
Currently, querying the state of an account (through the below RPC call)
{
"jsonrpc": "2.0",
"id": "dontcare",
"method": "query",
"params": {
"request_type": "view_account",
"finality": "final",
"account_id": "nearkat.testnet"
}
}
or requesting the contract’s state (through the following request)
{
"jsonrpc": "2.0",
"id": "dontcare",
"method": "query",
"params": {
"request_type": "view_state",
"finality": "final",
"account_id": "guest-book.testnet",
"prefix_base64": ""
}
}
returns the correct result, but no proof attached.
NOTE: There’s a field called proof
in the response for the view_state
endpoint, but in the example is shown as empty.
So, if this is already covered for the smart contract,
the section should be discarded.
The IBC standard requires that the NEAR relayer is able to feed our IBC framework running
in the Polkadot parachain with not only the data, but also the valid proofs that
help us to trustlessly validate that the state is correct.
3. Request block of transaction validations
The NEAR spec contains a detailed section on proof verification. It is possible
for a light client to validate whether a transaction or receipt happened on-chain.
For our purposes of bridging Polkadot with NEAR following the IBC’s (GitHub - cosmos/ibc: Interchain Standards (ICS) for the Cosmos network & interchain ecosystem.)
standard we are requesting the ability to validate a set of transactions as a batch
as opposed to singular verification. This is how IBC works in general, and there is a clear benefit to doing it
that way: proof-verification becomes less expensive as there is no need to re-calculate
hashes.
We would like to request an endpoint where a Vec is passed
and an adapted version of RpcLightClientExecutionProofResponse is returned, where we can still verify that a block_outcome_root matches the block_header_lite.inner_lite
for each transaction. And, as previously mentioned, there will be a reduction in total calculations of:
block_merkle_root = compute_root(block_hash, block_proof)
since we would be able to cache intermediate hashes within the compute_root calculation on the entire batch of transactions.