Skip to content

Commit

Permalink
Merge pull request #239 from oasisprotocol/CU-8692tq8gw_Remove-Ethers…
Browse files Browse the repository at this point in the history
…-5_Xi-Zhang

Remove Ethers v5
  • Loading branch information
CedarMist authored Jan 17, 2024
2 parents add1b7f + 5fc1d83 commit 62c0f20
Show file tree
Hide file tree
Showing 60 changed files with 6,844 additions and 3,660 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/contracts-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,9 @@ jobs:
- name: hardhat test examples/onchain-signer
working-directory: examples/onchain-signer
run: pnpm run test --network sapphire-localnet
- name: ethersv5-ts-esm
working-directory: examples/ethersv5-ts-esm
run: pnpm run test
- name: ethersv6-ts-esm
working-directory: examples/ethersv6-ts-esm
run: pnpm run test
22 changes: 8 additions & 14 deletions clients/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,36 @@
"lint": "prettier --cache --check . && eslint --ignore-path .gitignore .",
"format": "prettier --cache --write . && eslint --ignore-path .gitignore --fix .",
"build": "npm run build:esm && npm run build:cjs",
"clean": "rm -rf lib",
"build:esm": "tsc -p ./tsconfig.json",
"build:cjs": "tsc -p ./tsconfig.cjs.json && node scripts/rename-cjs",
"test": "jest",
"coverage": "jest --coverage",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/providers": "^5.7.1",
"@ethersproject/rlp": "^5.7.0",
"@oasisprotocol/deoxysii": "^0.0.5",
"cborg": "^1.9.5",
"ethers6": "npm:ethers@^6.6.1",
"ethers": "^6.6.1",
"js-sha512": "^0.8.0",
"tweetnacl": "^1.0.3",
"type-fest": "^2.19.0"
},
"devDependencies": {
"@ethersproject/transactions": "^5.7.0",
"@types/jest": "^28.1.8",
"@types/jest": "^29.5.11",
"@types/node": "^18.7.18",
"@types/node-fetch": "^2.6.2",
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"eslint": "^8.23.1",
"eslint-config-prettier": "^8.5.0",
"ethers5": "npm:ethers@^5.7.2",
"jest": "^28.1.3",
"nock": "^13.2.9",
"jest": "^29.7.0",
"nock": "^13.4.0",
"node-fetch": "^2.6.7",
"prettier": "^2.7.1",
"ts-jest": "^28.0.8",
"typedoc": "^0.25.1",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typedoc": "^0.25.4",
"typescript": "^4.8.3"
}
}
112 changes: 112 additions & 0 deletions clients/js/scripts/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createServer, IncomingMessage, ServerResponse } from 'node:http';

import { decodeRlp, getBytes } from 'ethers';

import * as cborg from 'cborg';

import fetch from 'node-fetch';
import { assert } from 'console';

async function getBody(request: IncomingMessage): Promise<string> {
return new Promise((resolve) => {
const bodyParts: Uint8Array[] = [];
let body: string;
request
.on('data', (chunk) => {
bodyParts.push(chunk);
})
.on('end', () => {
body = Buffer.concat(bodyParts).toString();
resolve(body);
});
});
}

createServer(onRequest).listen(3000);

interface JSONRPCRequest {
jsonrpc: string;
method: string;
params: any[];
id: number;
}

async function onRequest(req: IncomingMessage, response: ServerResponse) {
if (req.method != 'POST') {
response.writeHead(500, 'Not POST!');
response.end();
return;
}

const inputBody = JSON.parse(await getBody(req)) as JSONRPCRequest;
const encryptableMethods = [
'eth_estimateGas',
'eth_call',
'eth_sendRawTransaction',
];
const loggedMethods = encryptableMethods.concat(['oasis_callDataPublicKey']);

let bodies: JSONRPCRequest[];
if (Array.isArray(inputBody)) {
bodies = inputBody;
} else {
bodies = [inputBody];
}

for (const body of bodies) {
const log = loggedMethods.includes(body.method);

if (log) {
if (body.method == 'oasis_callDataPublicKey') {
console.log(req.method, req.url, body.method);
} else if (
body.method == 'eth_estimateGas' ||
body.method == 'eth_call'
) {
try {
const x = getBytes(body.params[0].data);
const y = cborg.decode(x);
// Verify envelope format == 1 (encrypted)
if ('data' in y) {
// EIP-712 signed queries are wrapped as follows:
// {data: {body{pk:,data:,nonce:},format:},leash:{nonce:,block_hash:,block_range:,block_number:},signature:}
assert(y.data.format == 1);
} else {
assert(y.format == 1);
}
console.log('ENCRYPTED', req.method, req.url, body.method);
} catch (e: any) {
console.log('NOT ENCRYPTED', req.method, req.url, body.method);
}
} else if (body.method == 'eth_sendRawTransaction') {
try {
const x = getBytes(body.params[0]);
const y = decodeRlp(x) as string[]; //console.log(pj);
const z = cborg.decode(getBytes(y[5]));
assert(z.format == 1); // Verify envelope format == 1 (encrypted)
console.log('ENCRYPTED', req.method, req.url, body.method);
} catch (e: any) {
console.log(
'NOT ENCRYPTED',
req.method,
req.url,
body.method,
body.params,
);
}
}
}
}

const pr = await fetch('http://127.0.0.1:8545/', {
method: 'POST',
body: JSON.stringify(inputBody),
headers: { 'Content-Type': 'application/json' },
});

const pj = await pr.json();

response.writeHead(200, 'OK');
response.write(JSON.stringify(pj));
response.end();
}
102 changes: 9 additions & 93 deletions clients/js/src/cipher.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import {
BytesLike,
arrayify,
hexlify,
isBytesLike,
} from '@ethersproject/bytes';
import * as cbor from 'cborg';
import { BytesLike, isBytesLike, hexlify, getBytes } from 'ethers';
import deoxysii from '@oasisprotocol/deoxysii';
import { IncomingMessage } from 'http';
import { sha512_256 } from 'js-sha512';
import nacl, { BoxKeyPair } from 'tweetnacl';
import { Promisable } from 'type-fest';

import { CallError, NETWORKS, OASIS_CALL_DATA_PUBLIC_KEY } from './index.js';
import { CallError } from './index.js';

export enum Kind {
Plain = 0,
Expand Down Expand Up @@ -66,7 +60,7 @@ export abstract class Cipher {
throw new Error('Attempted to sign tx having non-byteslike data.');
}
if (plaintext.length === 0) return; // Txs without data are just balance transfers, and all data in those is public.
const { data, nonce } = await this.encryptCallData(arrayify(plaintext));
const { data, nonce } = await this.encryptCallData(getBytes(plaintext));
const [format, pk] = await Promise.all([this.kind, this.publicKey]);
const body = pk.length && nonce.length ? { pk, nonce, data } : data;
if (format === Kind.Plain) return { body };
Expand Down Expand Up @@ -114,7 +108,7 @@ export abstract class Cipher {
/** Decrypts the data contained within a hex-encoded serialized envelope. */
public async decryptEncoded(callResult: BytesLike): Promise<string> {
return hexlify(
await this.decryptCallResult(cbor.decode(arrayify(callResult))),
await this.decryptCallResult(cbor.decode(getBytes(callResult))),
);
}

Expand All @@ -126,10 +120,10 @@ export abstract class Cipher {
}
if (res.fail) throw new CallError(formatFailure(res.fail), res.fail);
if (res.ok && (typeof res.ok === 'string' || res.ok instanceof Uint8Array))
return arrayify(res.ok);
return getBytes(res.ok);
const { nonce, data } = (res.ok as AeadEnvelope) ?? res.unknown;
const inner = cbor.decode(await this.decrypt(nonce, data));
if (inner.ok) return arrayify(inner.ok);
if (inner.ok) return getBytes(inner.ok);
if (inner.fail) throw new CallError(formatFailure(inner.fail), inner.fail);
throw new CallError(
`Unexpected inner call result: ${JSON.stringify(inner)}`,
Expand Down Expand Up @@ -184,18 +178,15 @@ export class X25519DeoxysII extends Cipher {
/** Creates a new cipher using an ephemeral keypair stored in memory. */
static ephemeral(peerPublicKey: BytesLike): X25519DeoxysII {
const keypair = nacl.box.keyPair();
return new X25519DeoxysII(
keypair,
arrayify(peerPublicKey, { allowMissingPrefix: true }),
);
return new X25519DeoxysII(keypair, getBytes(peerPublicKey));
}

static fromSecretKey(
secretKey: BytesLike,
peerPublicKey: BytesLike,
): X25519DeoxysII {
const keypair = nacl.box.keyPair.fromSecretKey(arrayify(secretKey));
return new X25519DeoxysII(keypair, arrayify(peerPublicKey));
const keypair = nacl.box.keyPair.fromSecretKey(getBytes(secretKey));
return new X25519DeoxysII(keypair, getBytes(peerPublicKey));
}

public constructor(keypair: BoxKeyPair, peerPublicKey: Uint8Array) {
Expand Down Expand Up @@ -277,78 +268,3 @@ export function lazy(generator: () => Promisable<Cipher>): Cipher {
},
) as Cipher;
}

export async function fetchRuntimePublicKeyByChainId(
chainId: number,
opts?: { fetch?: typeof fetch },
): Promise<Uint8Array> {
const { defaultGateway: gatewayUrl } = NETWORKS[chainId];
if (!gatewayUrl)
throw new Error(
`Unable to fetch runtime public key for network with unknown ID: ${chainId}.`,
);
const fetchImpl = globalThis?.fetch ?? opts?.fetch;
const res = await (fetchImpl
? fetchRuntimePublicKeyBrowser(gatewayUrl, fetchImpl)
: fetchRuntimePublicKeyNode(gatewayUrl));
return arrayify(res.result.key);
}

type CallDataPublicKeyResponse = {
result: { key: string; checksum: string; signature: string };
};

async function fetchRuntimePublicKeyNode(
gwUrl: string,
): Promise<CallDataPublicKeyResponse> {
// Import http or https, depending on the URI scheme.
const https = await import(/* webpackIgnore: true */ gwUrl.split(':')[0]);

const body = makeCallDataPublicKeyBody();
return new Promise((resolve, reject) => {
const opts = {
method: 'POST',
headers: {
'content-type': 'application/json',
'content-length': body.length,
},
};
const req = https.request(gwUrl, opts, (res: IncomingMessage) => {
const chunks: Buffer[] = [];
res.on('error', (err) => reject(err));
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
resolve(JSON.parse(Buffer.concat(chunks).toString()));
});
});
req.on('error', (err: Error) => reject(err));
req.write(body);
req.end();
});
}

async function fetchRuntimePublicKeyBrowser(
gwUrl: string,
fetchImpl: typeof fetch,
): Promise<CallDataPublicKeyResponse> {
const res = await fetchImpl(gwUrl, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: makeCallDataPublicKeyBody(),
});
if (!res.ok) {
throw new CallError('Failed to fetch runtime public key.', res);
}
return await res.json();
}

function makeCallDataPublicKeyBody(): string {
return JSON.stringify({
jsonrpc: '2.0',
id: Math.floor(Math.random() * 1e9),
method: OASIS_CALL_DATA_PUBLIC_KEY,
params: [],
});
}
Loading

0 comments on commit 62c0f20

Please sign in to comment.