Skip to content

Commit

Permalink
WASM Contract Verifier API for ChainIDE (#348)
Browse files Browse the repository at this point in the history
API endpoints:
* /verify/:network/:codeHash with package + signature parameter
* /info/:network/:codeHash
* Swagger: https://swagger.polkaholic.io/swagger.json

WASM Contract Verifier API Documentation:
* WASM-Verifier.md

Public test case:
* flipper.zip with `test/testWASMSignVerify.js`
* Verify Test: `curl -X POST -F "[email protected]" -F "signature=0xe209e220e80cc196f033e48a9e4a9bb178144372cdefb711a0ba211a1013fc3171d3b537fd3c3d2716753d664b7c6b3a3f887738672d484efd5d7da73bda89f71b" https://api.polkaholic.io/verify/shibuya/0x396418ff533172de8407004f53051b5409f6892564ddeba12f75cc855cb16fe0`
* Info Test: `curl "https://api.polkaholic.io/info/shibuya/0x396418ff533172de8407004f53051b5409f6892564ddeba12f75cc855cb16fe0"`
  • Loading branch information
sourabhniyogi committed Jun 4, 2023
1 parent 694ce07 commit 9b1e790
Show file tree
Hide file tree
Showing 17 changed files with 890 additions and 136 deletions.
129 changes: 129 additions & 0 deletions WASM-Verifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# ChainIDE-Polkaholic WASM Contract Verification API

This documents the WASM Contract API between ChainIDE and Polkaholic.io. It is a development stage effort in Summer 2023, supported by Inkubator parent bounty.

Other IDEs and block explorers may use the same paradigm and use the Polkaholic.io WASM Contract Verification API in the future.

* API Endpoint: [https://api.polkaholic.io](https://api.polkaholic.io)
* [swagger.json](https://swagger.polkaholic.io/swagger.json)

### Background

In principle, blockchain users of WASM Smart Contracts can trust a trustless smart contract because they can read source code, compile it themselves, and check that the compiled results
matches on-chain results. In practice, blockchain users won't do this -- they instead will rely on block explorers like Polkaholic.io to ensure that that third-party verifiers generated
the on-chain code correctly and verify their results.

### Flow

The flow developed here is:

* **Step 1: WASM Code Generation**. Developer compiles a WASM Contract and posts it on-chain within ChainIDE, with a `codeHash` of the RAW WASM Bytes targeting a specific network (e.g. Astar, Shiden, Shibuya)
* **Step 2: IDE Signing/Verify**. With developer permission, ChainIDE posts a _signed_ `codeHash` (`signature`) along with the source (`package`) to the Polkaholic `verify` API.
* **Step 3: Polkaholic Verification**. Polkaholic `verify` will check the signature against valid IDE addresses and store the results for viewing in its block explorer. Anyone can get the source code for any `codeHash` calling the Polkaholic `info` API endpoint

Because the WASM Code Generation is done by the developer already within ChainIDE, the above process is done in the amount of time it takes to post the zip file, which is less than 15 seconds. Note that this is different from the Sirato API, where the developer must wait for the Sirato verifier to complete its verification. It is believed that online IDEs may be strongly preferred for this reason.

### Public Test case

To support development

* WASM Source Test case: [flipper.zip](https://raw.github.com) with Code Hash `0x396418ff533172de8407004f53051b5409f6892564ddeba12f75cc855cb16fe0`
* Verify API Test case: with Signature `0xe209e220e80cc196f033e48a9e4a9bb178144372cdefb711a0ba211a1013fc3171d3b537fd3c3d2716753d664b7c6b3a3f887738672d484efd5d7da73bda89f71b`
```
curl -X POST -F "[email protected]" -F "signature=0xe209e220e80cc196f033e48a9e4a9bb178144372cdefb711a0ba211a1013fc3171d3b537fd3c3d2716753d664b7c6b3a3f887738672d484efd5d7da73bda89f71b" https://api.polkaholic.io/verify/shibuya/0x396418ff533172de8407004f53051b5409f6892564ddeba12f75cc855cb16fe0
```
* Info API Test case:
```
curl "https://api.polkaholic.io/info/shibuya/0x396418ff533172de8407004f53051b5409f6892564ddeba12f75cc855cb16fe0"
```

### In-depth Review of Flow - Public Test case

#### Step 1: WASM Code Generation

User compiles a WASM contract in an IDE, e.g. flipper.zip, which contains the WASM Contract metadata in a single file, e.g. flipper.contract with into code bytes.


#### Step 2. IDE Signing

***WASM Code Hash Computation/Signing.***

IDE computes the code hash programmaticaly (using blake2-256):

```
const codeHash = blake2AsHex(hexToU8a(wasm), 256)
console.log('WASM Code Hash:', codeHash, codeHash.length);
// WASM Code Hash: 0x396418ff533172de8407004f53051b5409f6892564ddeba12f75cc855cb16fe0 66
```

***Code Hash Signing***

IDE signs the `codeHash` bytes using an Ethereum wallet.

```
// sign codeHash using a openly private key (from https://wiki.polkadot.network/docs/learn-accounts)
let messageBytes = ethers.utils.toUtf8Bytes(codeHash);
const mnemonic = 'caution juice atom organ advance problem want pledge someone senior holiday very';
let validAddr = "0x58E0fB1aAB0B04Bd095AbcdF34484DA47Fe9fF77";
const wallet = ethers.Wallet.fromMnemonic(mnemonic);
const signature = await wallet.signMessage(messageBytes);
console.log("Private Key (Dev only):", mnemonic)
console.log('Signature:', signature, signature.length);
// Signature: 0xe209e220e80cc196f033e48a9e4a9bb178144372cdefb711a0ba211a1013fc3171d3b537fd3c3d2716753d664b7c6b3a3f887738672d484efd5d7da73bda89f71b 132
```

#### Step 3. Polkaholic.io Verification process

```
// verify signature by checking it matched validAddr "0x58E0fB1aAB0B04Bd095AbcdF34484DA47Fe9fF77";
const signerAddr = await ethers.utils.verifyMessage(codeHash, signature);
console.log("Signer Address:", signerAddr);
if (signerAddr == validAddr) {
console.log("PASS");
} else {
console.log("FAIL", signerAddr, "should be", validAddr);
}
// Signer Address: 0x58E0fB1aAB0B04Bd095AbcdF34484DA47Fe9fF77
```

Note the above private key mnemonic / validAddr is for development / test purposes only.

Production will only expose a set of public addresses from the IDE (like 1-2 per IDE). We may expose in open source code safely.

Multiple IDEs can be supported, but ChainIDE is the only case we consider at this point.

#### Polkaholic Verification API Implementation

Core implmentation is in query.js `postChainWASMContractVerification`, used in api.js

The Verification API will return an 4XX error:
* invalid network
* TODO: invalid size of `codeHash` or `signature`
* the `signature` does not match the `codeHash` and resolve to a set of valid addresses
* the zip file is unzippable into a local `/tmp` directory (not a zip file, excessive size, etc.)
* there is no .contract file in the zip file which form the metadata
* there is no WASM code in the metadata
* the provided `codeHash` does not match the actual hash of the WASM Code

## Security Considerations

The above process depends on the IDE's ability to keep its secret key
private. Should the IDE key be compromised, any submissions after
this time should be considered suspect and be resubmitted / reverified
according to a different public address. Polkaholic records the
verification time, signature, and verifier address to support this
potential scenario.

A background audit could be conducted against a second verifier
(Sirato) or additional verifiers, who could each attest to the
validity of the zip file by signing the codeHash. This is not
reasonable to do at this time, because the number of datapoints are
small, and it is believed the results would not be available for any
WASM Contract for several minutes to up to an hour. However, once
there are a dozen ChainIDE verified contracts on chain (that are more
sophisticated than flipper), this process is reasonable to generate
statistics for, and we expect to document the results. Failure to
generate consensus between ChainIDE and Sirato will be raised with
ChainIDE.


51 changes: 49 additions & 2 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const paraTool = require('./substrate/paraTool');
const uiTool = require('./substrate/uiTool');
const port = 3001
const Query = require("./substrate/query");

const cookieParser = require("cookie-parser");
const multer = require('multer');

var debugLevel = paraTool.debugTracing
var query = new Query(debugLevel);
Expand Down Expand Up @@ -391,6 +391,53 @@ app.get('/wasmcontracts/:chainID_or_chainName', async (req, res) => {
}
})

// Get information on verification status of any codeHash, whether uploaded or not
app.get('/info/:network/:codeHash?', async (req, res) => {
try {
let network = req.params["network"]
let codeHash = req.params["codeHash"] ? req.params["codeHash"] : null;
let info = await query.getChainWASMCodeInfo(network, codeHash);
if (info) {
res.write(JSON.stringify(info));
await query.tallyAPIKey(getapikey(req));
res.end();
} else {
res.sendStatus(404);
}
} catch (err) {
return res.status(400).json({
error: err.toString()
});
}
})

const upload = multer({
dest: '/tmp/'
});

// Receives VERIFIED source code package with authentication mechanism from IDE -- see WASM-verifier.md
app.post('/verify/:network/:codeHash', upload.single('package'), async (req, res) => {
try {
let packageFile = req.file; // File object
let signature = req.body.signature; // Signature value
let network = req.params["network"]
let codeHash = req.params["codeHash"]
let result = await query.postChainWASMContractVerification(network, codeHash, packageFile, signature);
if (result) {
res.write(JSON.stringify(result));
await query.tallyAPIKey(getapikey(req));
res.end();
} else {
res.sendStatus(400);
}

} catch (err) {
return res.status(400).json({
error: err.toString()
});
}
})

// Usage: https://api.polkaholic.io/specversions/polkadot
app.get('/specversions/:chainID_or_chainName', async (req, res) => {
try {
Expand Down Expand Up @@ -1019,4 +1066,4 @@ Promise.all([x]).then(() => {
query.autoUpdate()
}).catch(err => {
// handle error here
});
});
111 changes: 111 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@zeitgeistpm/type-defs": "^0.4.5",
"abi-decoder": "^2.4.0",
"ably": "^1.2.33",
"adm-zip": "^0.5.10",
"as-scale-codec": "^0.2.3",
"async-lock": "^1.3.0",
"async-mutex": "^0.4.0",
Expand Down Expand Up @@ -67,6 +68,7 @@
"lodash": "^4.17.21",
"memcached-promise": "^1.0.1",
"moonbeam-types-bundle": "^2.0.4",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3-rc.0",
"node-ini": "^1.0.0",
"nodemailer": "^6.7.5",
Expand Down
4 changes: 2 additions & 2 deletions substrate/crawler.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ module.exports = class Crawler extends Indexer {
if (crawlTrace && chain.RPCBackfill && (chain.RPCBackfill.length > 0)) {
trace = await this.crawlTrace(chain, blockHash, 60000 * (t.attempted + 1));
console.log("crawl_block_trace trace", trace.length);
}else{
} else {
console.log(`[crawlTrace=${crawlTrace}] crawl_block_trace chain.RPCBackfill NOT OK`, chain.RPCBackfill);
}

Expand Down Expand Up @@ -2642,4 +2642,4 @@ module.exports = class Crawler extends Indexer {
return [unsubscribeFinalizedHeads, unsubscribeStorage, unsubscribeRuntimeVersion];
}

}
}
Loading

0 comments on commit 9b1e790

Please sign in to comment.