UNPKG

@oasisprotocol/sapphire-paratime

Version:
149 lines 6.17 kB
"use strict"; // SPDX-License-Identifier: Apache-2.0 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KeyFetcher = exports.fetchRuntimePublicKey = exports.FetchError = void 0; const cborg_1 = require("cborg"); const ethersutils_js_1 = require("./ethersutils.cjs"); const constants_js_1 = require("./constants.cjs"); const cipher_js_1 = require("./cipher.cjs"); /** * calldata public keys are cached for this amount of time * This prevents frequent unnecessary re-fetches * This time is in milliseconds */ const DEFAULT_PUBKEY_CACHE_EXPIRATION_MS = 60 * 5 * 1000; class FetchError extends Error { constructor(message, response) { super(message); this.response = response; } } exports.FetchError = FetchError; function parseBigIntFromByteArray(bytes) { const eight = BigInt(8); return bytes.reduce((acc, byte) => (acc << eight) | BigInt(byte), BigInt(0)); } class AbiDecodeError extends Error { } /// Manual ABI-parsing of ['uint', 'bytes'] function parseAbiEncodedUintBytes(bytes) { if (bytes.length < 32 * 3) { throw new AbiDecodeError('too short'); } const status = parseBigIntFromByteArray(bytes.slice(0, 32)); const offset = Number(parseBigIntFromByteArray(bytes.slice(32, 64))); if (bytes.length < offset + 32) { throw new AbiDecodeError('too short, offset'); } const data_length = Number(parseBigIntFromByteArray(bytes.slice(offset, offset + 32))); if (bytes.length < offset + 32 + data_length) { throw new AbiDecodeError('too short, data'); } const data = bytes.slice(offset + 32, offset + 32 + data_length); return [status, data]; } /** * Picks the most user-trusted runtime calldata public key source based on what * connections are available. * * NOTE: MetaMask does not support Web3 methods it doesn't know about, so we * have to fall-back to fetch()ing directly via the default chain gateway. */ function fetchRuntimePublicKey(args) { return __awaiter(this, void 0, void 0, function* () { let chainId = undefined; const { upstream } = args; chainId = (0, ethersutils_js_1.fromQuantity)((yield upstream.request({ method: 'eth_chainId', }))); // NOTE: we hard-code the eth_call data, as it never changes // It's equivalent to: // abi_encode(['string', 'bytes'], ['core.CallDataPublicKey', cborEncode(null)]) const call_resp = (yield upstream.request({ method: 'eth_call', params: [ { to: constants_js_1.SUBCALL_ADDR, data: constants_js_1.CALLDATAPUBLICKEY_CALLDATA, }, 'latest', ], })); if (call_resp === '0x') { throw new Error(`fetchRuntimePublicKey - invalid response: ${call_resp}`); } const resp_bytes = (0, ethersutils_js_1.getBytes)(call_resp); // NOTE: to avoid pulling-in a full ABI decoder dependency, slice it manually const [resp_status, resp_cbor] = parseAbiEncodedUintBytes(resp_bytes); if (resp_status !== BigInt(0)) { throw new Error(`fetchRuntimePublicKey - invalid status: ${resp_status}`); } const response = (0, cborg_1.decode)(resp_cbor); return { key: response.public_key.key, checksum: response.public_key.checksum, signature: response.public_key.signature, epoch: response.epoch, chainId, fetched: new Date(), }; }); } exports.fetchRuntimePublicKey = fetchRuntimePublicKey; /** * Retrieves calldata public keys from RPC provider */ class KeyFetcher { constructor(timeoutMilliseconds = DEFAULT_PUBKEY_CACHE_EXPIRATION_MS) { this.timeoutMilliseconds = timeoutMilliseconds; } /** * Retrieve cached key if possible, otherwise fetch a fresh one * * @param upstream Upstream ETH JSON-RPC provider * @returns calldata public key */ fetch(upstream) { return __awaiter(this, void 0, void 0, function* () { if (upstream === undefined) { throw new Error('fetch() Upstream must not be undefined!'); } if (this.pubkey) { const pk = this.pubkey; const expiry = Date.now() - this.timeoutMilliseconds; if (pk.fetched && pk.fetched.valueOf() >= expiry) { // XXX: if provider switch chain, may return cached key for wrong chain return pk; } } return (this.pubkey = yield fetchRuntimePublicKey({ upstream })); }); } cipher(upstream) { return __awaiter(this, void 0, void 0, function* () { const { key, epoch } = yield this.fetch(upstream).catch((error) => { // Log error to help debug: rainbowkit swallowed err if getChainId called this during connectToWallet console.error('KeyFetcher.cipher failed', error); throw error; }); return cipher_js_1.X25519DeoxysII.ephemeral(key, epoch); }); } cipherSync() { if (!this.pubkey) { throw new Error('No cached pubkey!'); } const { key, epoch } = this.pubkey; return cipher_js_1.X25519DeoxysII.ephemeral(key, epoch); } } exports.KeyFetcher = KeyFetcher; //# sourceMappingURL=calldatapublickey.js.map