@bug-fix/drand-client
Version:
A client to the drand randomness beacon network.
809 lines (792 loc) • 29.3 kB
JavaScript
'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;