@oasisprotocol/sapphire-paratime
Version:
The Sapphire ParaTime Web3 integration library.
149 lines • 6.17 kB
JavaScript
;
// 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