UNPKG

@bug-fix/drand-client

Version:

A client to the drand randomness beacon network.

809 lines (792 loc) 29.3 kB
'use strict'; var bls12381 = require('@noble/curves/bls12-381'); var utils = require('@noble/curves/abstract/utils'); var buffer = require('buffer'); const LIB_VERSION = "1.2.6-fix.11"; function sleep(timeMs) { return new Promise((resolve) => { if (timeMs <= 0) { resolve(); } setTimeout(resolve, timeMs); }); } function roundAt(time, chain) { if (!Number.isFinite(time)) { throw new Error("Cannot use Infinity or NaN as a beacon time"); } if (time < chain.genesis_time * 1e3) { throw Error("Cannot request a round before the genesis time"); } return Math.floor((time - chain.genesis_time * 1e3) / (chain.period * 1e3)) + 1; } function roundTime(chain, round) { if (!Number.isFinite(round)) { throw new Error("Cannot use Infinity or NaN as a round number"); } round = round < 0 ? 0 : round; return (chain.genesis_time + (round - 1) * chain.period) * 1e3; } const defaultHttpOptions = { userAgent: `drand-client-${LIB_VERSION}` }; async function jsonOrError(url, options = defaultHttpOptions) { const headers = { ...options.headers }; if (options.userAgent) { headers["User-Agent"] = options.userAgent; } const response = await fetch(url, { headers }); if (!response.ok) { throw Error(`Error response fetching ${url} - got ${response.status}`); } return await response.json(); } async function retryOnError(fn, times) { try { return await fn(); } catch (err) { if (times === 0) { throw err; } return retryOnError(fn, times - 1); } } class HttpChain { constructor(baseUrl, options = defaultChainOptions, httpOptions = {}) { this.baseUrl = baseUrl; this.options = options; this.httpOptions = httpOptions; } async info() { const chainInfo = await jsonOrError(`${this.baseUrl}/info`, this.httpOptions); if (!!this.options.chainVerificationParams && !isValidInfo(chainInfo, this.options.chainVerificationParams)) { throw Error(`The chain info retrieved from ${this.baseUrl} did not match the verification params!`); } return chainInfo; } } function isValidInfo(chainInfo, validParams) { return chainInfo.hash === validParams.chainHash && chainInfo.public_key === validParams.publicKey; } class HttpCachingChain { constructor(baseUrl, options = defaultChainOptions) { this.baseUrl = baseUrl; this.options = options; this.chain = new HttpChain(baseUrl, options); } chain; cachedInfo; async info() { if (!this.cachedInfo) { this.cachedInfo = await this.chain.info(); } return this.cachedInfo; } } class HttpChainClient { constructor(someChain, options = defaultChainOptions, httpOptions = defaultHttpOptions) { this.someChain = someChain; this.options = options; this.httpOptions = httpOptions; } async get(roundNumber) { const url = withCachingParams(`${this.someChain.baseUrl}/public/${roundNumber}`, this.options); return await jsonOrError(url, this.httpOptions); } async latest() { const url = withCachingParams(`${this.someChain.baseUrl}/public/latest`, this.options); return await jsonOrError(url, this.httpOptions); } chain() { return this.someChain; } } function withCachingParams(url, config) { if (config.noCache) { return `${url}?${Date.now()}`; } return url; } function createSpeedTest(test, frequencyMs, samples = 5) { let queue = new DroppingQueue(samples); let intervalId = null; const executeSpeedTest = async () => { const startTime = Date.now(); try { await test(); queue.add(Date.now() - startTime); } catch (err) { queue.add(Number.MAX_SAFE_INTEGER); } }; return { start: () => { if (intervalId != null) { console.warn("Attempted to start a speed test, but it had already been started!"); return; } intervalId = setInterval(executeSpeedTest, frequencyMs); }, stop: () => { if (intervalId !== null) { clearInterval(intervalId); intervalId = null; queue = new DroppingQueue(samples); } }, average: () => { const values = queue.get(); if (values.length === 0) { return Number.MAX_SAFE_INTEGER; } const total = values.reduce((acc, next) => acc + next, 0); return total / values.length; } }; } class DroppingQueue { constructor(capacity) { this.capacity = capacity; } values = []; add(value) { this.values.push(value); if (this.values.length > this.capacity) { this.values.pop(); } } get() { return this.values; } } const defaultSpeedTestInterval = 1e3 * 60 * 5; class FastestNodeClient { constructor(baseUrls, options = defaultChainOptions, speedTestIntervalMs = defaultSpeedTestInterval) { this.baseUrls = baseUrls; this.options = options; this.speedTestIntervalMs = speedTestIntervalMs; if (baseUrls.length === 0) { throw Error("Can't optimise an empty `baseUrls` array!"); } } speedTests = []; speedTestHttpOptions = { userAgent: "drand-web-client-speedtest" }; async latest() { return new HttpChainClient(this.current(), this.options).latest(); } async get(roundNumber) { return new HttpChainClient(this.current(), this.options).get(roundNumber); } chain() { return this.current(); } start() { if (this.baseUrls.length === 1) { console.warn("There was only a single base URL in the `FastestNodeClient` - not running speed testing"); return; } this.speedTests = this.baseUrls.map( (url) => { const testFn = async () => { await new HttpChain(url, this.options, this.speedTestHttpOptions).info(); return; }; const test = createSpeedTest(testFn, this.speedTestIntervalMs); test.start(); return { test, url }; } ); } current() { if (this.speedTests.length === 0) { console.warn("You are not currently running speed tests to choose the fastest client. Run `.start()` to speed test"); } const fastestEntry = this.speedTests.slice().sort((entry1, entry2) => entry1.test.average() - entry2.test.average()).shift(); if (!fastestEntry) { throw Error("Somehow there were no entries to optimise! This should be impossible by now"); } return new HttpCachingChain(fastestEntry.url, this.options); } stop() { this.speedTests.forEach((entry) => entry.test.stop()); this.speedTests = []; } } class MultiBeaconNode { constructor(baseUrl, options = defaultChainOptions) { this.baseUrl = baseUrl; this.options = options; } async chains() { const chains = await jsonOrError(`${this.baseUrl}/chains`); if (!Array.isArray(chains)) { throw Error(`Expected an array from the chains endpoint but got: ${chains}`); } return chains.map((chainHash) => new HttpCachingChain(`${this.baseUrl}/${chainHash}`), this.options); } async health() { const response = await fetch(`${this.baseUrl}/health`); if (!response.ok) { return { status: response.status, current: -1, expected: -1 }; } const json = await response.json(); return { status: response.status, current: json.current ?? -1, expected: json.expected ?? -1 }; } } // copied from utils function isBytes(a) { return (a instanceof Uint8Array || (a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')); } function bytes(b, ...lengths) { if (!isBytes(b)) throw new Error('Uint8Array expected'); if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error(`Uint8Array expected of length ${lengths}, not of length=${b.length}`); } function exists(instance, checkFinished = true) { if (instance.destroyed) throw new Error('Hash instance has been destroyed'); if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called'); } function output(out, instance) { bytes(out); const min = instance.outputLen; if (out.length < min) { throw new Error(`digestInto() expects output buffer of length at least ${min}`); } } /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+. // node.js versions earlier than v19 don't declare it in global scope. // For node.js, package.json#exports field mapping rewrites import // from `crypto` to `cryptoNode`, which imports native module. // Makes the utils un-importable in browsers without a bundler. // Once node.js 18 is deprecated (2025-04-30), we can just drop the import. // Cast array to view const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength); // The rotate right (circular right shift) operation for uint32 const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift); new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44; /** * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99]) */ function utf8ToBytes(str) { if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`); return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809 } /** * Normalizes (non-hex) string or Uint8Array to Uint8Array. * Warning: when Uint8Array is passed, it would NOT get copied. * Keep in mind for future mutable operations. */ function toBytes(data) { if (typeof data === 'string') data = utf8ToBytes(data); bytes(data); return data; } // For runtime check if class implements interface class Hash { // Safe version that clones internal state clone() { return this._cloneInto(); } } function wrapConstructor(hashCons) { const hashC = (msg) => hashCons().update(toBytes(msg)).digest(); const tmp = hashCons(); hashC.outputLen = tmp.outputLen; hashC.blockLen = tmp.blockLen; hashC.create = () => hashCons(); return hashC; } // Polyfill for Safari 14 function setBigUint64(view, byteOffset, value, isLE) { if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE); const _32n = BigInt(32); const _u32_max = BigInt(0xffffffff); const wh = Number((value >> _32n) & _u32_max); const wl = Number(value & _u32_max); const h = isLE ? 4 : 0; const l = isLE ? 0 : 4; view.setUint32(byteOffset + h, wh, isLE); view.setUint32(byteOffset + l, wl, isLE); } // Choice: a ? b : c const Chi = (a, b, c) => (a & b) ^ (~a & c); // Majority function, true if any two inpust is true const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c); /** * Merkle-Damgard hash construction base class. * Could be used to create MD5, RIPEMD, SHA1, SHA2. */ class HashMD extends Hash { constructor(blockLen, outputLen, padOffset, isLE) { super(); this.blockLen = blockLen; this.outputLen = outputLen; this.padOffset = padOffset; this.isLE = isLE; this.finished = false; this.length = 0; this.pos = 0; this.destroyed = false; this.buffer = new Uint8Array(blockLen); this.view = createView(this.buffer); } update(data) { exists(this); const { view, buffer, blockLen } = this; data = toBytes(data); const len = data.length; for (let pos = 0; pos < len;) { const take = Math.min(blockLen - this.pos, len - pos); // Fast path: we have at least one block in input, cast it to view and process if (take === blockLen) { const dataView = createView(data); for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos); continue; } buffer.set(data.subarray(pos, pos + take), this.pos); this.pos += take; pos += take; if (this.pos === blockLen) { this.process(view, 0); this.pos = 0; } } this.length += data.length; this.roundClean(); return this; } digestInto(out) { exists(this); output(out, this); this.finished = true; // Padding // We can avoid allocation of buffer for padding completely if it // was previously not allocated here. But it won't change performance. const { buffer, view, blockLen, isLE } = this; let { pos } = this; // append the bit '1' to the message buffer[pos++] = 0b10000000; this.buffer.subarray(pos).fill(0); // we have less than padOffset left in buffer, so we cannot put length in // current block, need process it and pad again if (this.padOffset > blockLen - pos) { this.process(view, 0); pos = 0; } // Pad until full block byte with zeros for (let i = pos; i < blockLen; i++) buffer[i] = 0; // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen. // So we just write lowest 64 bits of that value. setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE); this.process(view, 0); const oview = createView(out); const len = this.outputLen; // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT if (len % 4) throw new Error('_sha2: outputLen should be aligned to 32bit'); const outLen = len / 4; const state = this.get(); if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state'); for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE); } digest() { const { buffer, outputLen } = this; this.digestInto(buffer); const res = buffer.slice(0, outputLen); this.destroy(); return res; } _cloneInto(to) { to || (to = new this.constructor()); to.set(...this.get()); const { blockLen, buffer, length, finished, destroyed, pos } = this; to.length = length; to.pos = pos; to.finished = finished; to.destroyed = destroyed; if (length % blockLen) to.buffer.set(buffer); return to; } } // SHA2-256 need to try 2^128 hashes to execute birthday attack. // BTC network is doing 2^67 hashes/sec as per early 2023. // Round constants: // first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) // prettier-ignore const SHA256_K = /* @__PURE__ */ new Uint32Array([ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]); // Initial state: // first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 // prettier-ignore const SHA256_IV = /* @__PURE__ */ new Uint32Array([ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]); // Temporary buffer, not used to store anything between runs // Named this way because it matches specification. const SHA256_W = /* @__PURE__ */ new Uint32Array(64); class SHA256 extends HashMD { constructor() { super(64, 32, 8, false); // We cannot use array here since array allows indexing by variable // which means optimizer/compiler cannot use registers. this.A = SHA256_IV[0] | 0; this.B = SHA256_IV[1] | 0; this.C = SHA256_IV[2] | 0; this.D = SHA256_IV[3] | 0; this.E = SHA256_IV[4] | 0; this.F = SHA256_IV[5] | 0; this.G = SHA256_IV[6] | 0; this.H = SHA256_IV[7] | 0; } get() { const { A, B, C, D, E, F, G, H } = this; return [A, B, C, D, E, F, G, H]; } // prettier-ignore set(A, B, C, D, E, F, G, H) { this.A = A | 0; this.B = B | 0; this.C = C | 0; this.D = D | 0; this.E = E | 0; this.F = F | 0; this.G = G | 0; this.H = H | 0; } process(view, offset) { // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false); for (let i = 16; i < 64; i++) { const W15 = SHA256_W[i - 15]; const W2 = SHA256_W[i - 2]; const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3); const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10); SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0; } // Compression function main loop, 64 rounds let { A, B, C, D, E, F, G, H } = this; for (let i = 0; i < 64; i++) { const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25); const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0; const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22); const T2 = (sigma0 + Maj(A, B, C)) | 0; H = G; G = F; F = E; E = (D + T1) | 0; D = C; C = B; B = A; A = (T1 + T2) | 0; } // Add the compressed chunk to the current hash value A = (A + this.A) | 0; B = (B + this.B) | 0; C = (C + this.C) | 0; D = (D + this.D) | 0; E = (E + this.E) | 0; F = (F + this.F) | 0; G = (G + this.G) | 0; H = (H + this.H) | 0; this.set(A, B, C, D, E, F, G, H); } roundClean() { SHA256_W.fill(0); } destroy() { this.set(0, 0, 0, 0, 0, 0, 0, 0); this.buffer.fill(0); } } /** * SHA2-256 hash function * @param message - data that would be hashed */ const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256()); async function verifyBeacon(chainInfo, beacon, expectedRound) { const publicKey = chainInfo.public_key; if (beacon.round !== expectedRound) { console.error("round was not the expected round"); return false; } if (!await randomnessIsValid(beacon)) { console.error("randomness did not match the signature"); return false; } if (isChainedBeacon(beacon, chainInfo)) { return bls12381.bls12_381.verify(beacon.signature, await chainedBeaconMessage(beacon), publicKey); } if (isUnchainedBeacon(beacon, chainInfo)) { return bls12381.bls12_381.verify(beacon.signature, await unchainedBeaconMessage(beacon), publicKey); } if (isG1G2SwappedBeacon(beacon, chainInfo)) { return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey); } if (isG1Rfc9380(beacon, chainInfo)) { return verifySigOnG1(beacon.signature, await unchainedBeaconMessage(beacon), publicKey, "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"); } console.error(`Beacon type ${chainInfo.schemeID} was not supported or the beacon was not of the purported type`); return false; } function normP1(point) { return point instanceof bls12381.bls12_381.G1.ProjectivePoint ? point : bls12381.bls12_381.G1.ProjectivePoint.fromHex(point); } function normP2(point) { return point instanceof bls12381.bls12_381.G2.ProjectivePoint ? point : bls12381.bls12_381.G2.ProjectivePoint.fromHex(point); } function normP1Hash(point, domainSeparationTag) { return point instanceof bls12381.bls12_381.G1.ProjectivePoint ? point : bls12381.bls12_381.G1.hashToCurve(utils.ensureBytes("point", point), { DST: domainSeparationTag }); } async function verifySigOnG1(signature, message, publicKey, domainSeparationTag = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_") { const P = normP2(publicKey); const Hm = normP1Hash(message, domainSeparationTag); const G = bls12381.bls12_381.G2.ProjectivePoint.BASE; const S = normP1(signature); const ePHm = bls12381.bls12_381.pairing(Hm, P.negate(), true); const eGS = bls12381.bls12_381.pairing(S, G, true); const exp = bls12381.bls12_381.fields.Fp12.mul(eGS, ePHm); return bls12381.bls12_381.fields.Fp12.eql(exp, bls12381.bls12_381.fields.Fp12.ONE); } async function chainedBeaconMessage(beacon) { const message = buffer.Buffer.concat([ signatureBuffer(beacon.previous_signature), roundBuffer(beacon.round) ]); return sha256(message); } async function unchainedBeaconMessage(beacon) { return sha256(roundBuffer(beacon.round)); } function signatureBuffer(sig) { return buffer.Buffer.from(sig, "hex"); } function roundBuffer(round) { const buffer$1 = buffer.Buffer.alloc(8); buffer$1.writeBigUInt64BE(BigInt(round)); return buffer$1; } async function randomnessIsValid(beacon) { const expectedRandomness = sha256(buffer.Buffer.from(beacon.signature, "hex")); return buffer.Buffer.from(beacon.randomness, "hex").compare(expectedRandomness) == 0; } const DEFAULT_CHAIN_URL = "https://api.drand.sh"; const DEFAULT_CHAIN_INFO = { public_key: "868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31", period: 30, genesis_time: 1595431050, hash: "8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce", groupHash: "176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a", schemeID: "pedersen-bls-chained", metadata: { "beaconID": "default" } }; const QUICKNET_CHAIN_URL = "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"; const QUICKNET_CHAIN_INFO = { public_key: "83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a", period: 3, genesis_time: 1692803367, hash: "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", groupHash: "f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e", schemeID: "bls-unchained-g1-rfc9380", metadata: { beaconID: "quicknet" } }; const FASTNET_CHAIN_URL = "https://api.drand.sh/dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493"; const FASTNET_CHAIN_INFO = { hash: "dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493", public_key: "a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e", period: 3, genesis_time: 1677685200, groupHash: "a81e9d63f614ccdb144b8ff79fbd4d5a2d22055c0bfe4ee9a8092003dab1c6c0", schemeID: "bls-unchained-on-g1", metadata: { beaconID: "fastnet" } }; const TESTNET_DEFAULT_CHAIN_URL = "https://pl-us.testnet.drand.sh"; const TESTNET_DEFAULT_CHAIN_INFO = { public_key: "922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df", period: 25, genesis_time: 1590445175, hash: "84b2234fb34e835dccd048255d7ad3194b81af7d978c3bf157e3469592ae4e02", groupHash: "4dd408e5fdff9323c76a9b6f087ba8fdc5a6da907bd9217d9d10f2287d081957", schemeID: "pedersen-bls-chained", metadata: { beaconID: "default" } }; const TESTNET_QUICKNET_CHAIN_URL = "https://pl-us.testnet.drand.sh/cc9c398442737cbd141526600919edd69f1d6f9b4adb67e4d912fbc64341a9a5"; const TESTNET_QUICKNET_CHAIN_INFO = { public_key: "b15b65b46fb29104f6a4b5d1e11a8da6344463973d423661bb0804846a0ecd1ef93c25057f1c0baab2ac53e56c662b66072f6d84ee791a3382bfb055afab1e6a375538d8ffc451104ac971d2dc9b168e2d3246b0be2015969cbaac298f6502da", period: 3, genesis_time: 1689232296, hash: "cc9c398442737cbd141526600919edd69f1d6f9b4adb67e4d912fbc64341a9a5", groupHash: "40d49d910472d4adb1d67f65db8332f11b4284eecf05c05c5eacd5eef7d40e2d", schemeID: "bls-unchained-g1-rfc9380", metadata: { beaconID: "quicknet-t" } }; function defaultClient() { const opts = { ...defaultChainOptions, chainVerificationParams: { chainHash: DEFAULT_CHAIN_INFO.hash, publicKey: DEFAULT_CHAIN_INFO.public_key } }; const chain = new HttpCachingChain(DEFAULT_CHAIN_URL, opts); return new HttpChainClient(chain, opts); } function quicknetClient() { const opts = { ...defaultChainOptions, chainVerificationParams: { chainHash: QUICKNET_CHAIN_INFO.hash, publicKey: QUICKNET_CHAIN_INFO.public_key } }; const chain = new HttpCachingChain(QUICKNET_CHAIN_URL, opts); return new HttpChainClient(chain, opts); } function fastnetClient() { const opts = { ...defaultChainOptions, chainVerificationParams: { chainHash: FASTNET_CHAIN_INFO.hash, publicKey: FASTNET_CHAIN_INFO.public_key } }; const chain = new HttpCachingChain(FASTNET_CHAIN_URL, opts); return new HttpChainClient(chain, opts); } function testnetDefaultClient() { const opts = { ...defaultChainOptions, chainVerificationParams: { chainHash: TESTNET_DEFAULT_CHAIN_INFO.hash, publicKey: TESTNET_DEFAULT_CHAIN_INFO.public_key } }; const chain = new HttpCachingChain(TESTNET_DEFAULT_CHAIN_URL, opts); return new HttpChainClient(chain, opts); } function testnetQuicknetClient() { const opts = { ...defaultChainOptions, chainVerificationParams: { chainHash: TESTNET_QUICKNET_CHAIN_INFO.hash, publicKey: TESTNET_QUICKNET_CHAIN_INFO.public_key } }; const chain = new HttpCachingChain(TESTNET_QUICKNET_CHAIN_URL, opts); return new HttpChainClient(chain, opts); } const defaultChainOptions = { disableBeaconVerification: false, noCache: false }; async function fetchBeacon(client, roundNumber) { if (!roundNumber) { roundNumber = roundAt(Date.now(), await client.chain().info()); } if (roundNumber < 1) { throw Error("Cannot request lower than round number 1"); } const beacon = await client.get(roundNumber); return validatedBeacon(client, beacon, roundNumber); } async function fetchBeaconByTime(client, time) { const info = await client.chain().info(); const roundNumber = roundAt(time, info); return fetchBeacon(client, roundNumber); } async function* watch(client, abortController, options = defaultWatchOptions) { const info = await client.chain().info(); let currentRound = roundAt(Date.now(), info); while (!abortController.signal.aborted) { const now = Date.now(); await sleep(roundTime(info, currentRound) - now); const beacon = await retryOnError(async () => client.get(currentRound), options.retriesOnFailure); yield validatedBeacon(client, beacon, currentRound); currentRound = currentRound + 1; } } const defaultWatchOptions = { retriesOnFailure: 3 }; async function validatedBeacon(client, beacon, expectedRound) { if (client.options.disableBeaconVerification) { return beacon; } const info = await client.chain().info(); if (!await verifyBeacon(info, beacon, expectedRound)) { throw Error("The beacon retrieved was not valid!"); } return beacon; } function isChainedBeacon(value, info) { return info.schemeID === "pedersen-bls-chained" && !!value.previous_signature && !!value.randomness && !!value.signature && value.round > 0; } function isUnchainedBeacon(value, info) { return info.schemeID === "pedersen-bls-unchained" && !!value.randomness && !!value.signature && value.previous_signature === void 0 && value.round > 0; } function isG1G2SwappedBeacon(value, info) { return info.schemeID === "bls-unchained-on-g1" && !!value.randomness && !!value.signature && value.previous_signature === void 0 && value.round > 0; } function isG1Rfc9380(value, info) { return info.schemeID === "bls-unchained-g1-rfc9380" && !!value.randomness && !!value.signature && value.previous_signature === void 0 && value.round > 0; } exports.FastestNodeClient = FastestNodeClient; exports.HttpCachingChain = HttpCachingChain; exports.HttpChain = HttpChain; exports.HttpChainClient = HttpChainClient; exports.MultiBeaconNode = MultiBeaconNode; exports.defaultChainOptions = defaultChainOptions; exports.defaultClient = defaultClient; exports.fastnetClient = fastnetClient; exports.fetchBeacon = fetchBeacon; exports.fetchBeaconByTime = fetchBeaconByTime; exports.isChainedBeacon = isChainedBeacon; exports.isG1G2SwappedBeacon = isG1G2SwappedBeacon; exports.isG1Rfc9380 = isG1Rfc9380; exports.isUnchainedBeacon = isUnchainedBeacon; exports.quicknetClient = quicknetClient; exports.roundAt = roundAt; exports.roundTime = roundTime; exports.testnetDefaultClient = testnetDefaultClient; exports.testnetQuicknetClient = testnetQuicknetClient; exports.watch = watch;