(function () { 'use strict'; // log provides a wrapper for console.log that prefixes '[libkernel]' to the // output. function log(...inputs) { console.log("[libkernel]", ...inputs); } // logErr provides a wrapper for console.error that prefixes '[libkernel]' to // the output. function logErr(...inputs) { console.error("[libkernel]", ...inputs); } // objAsString will try to return the provided object as a string. If the // object is already a string, it will be returned without modification. If the // object is an 'Error', the message of the error will be returned. If the object // has a toString method, the toString method will be called and the result // will be returned. If the object is null or undefined, a special string will // be returned indicating that the undefined/null object cannot be converted to // a string. In all other cases, JSON.stringify is used. If JSON.stringify // throws an exception, a message "[could not provide object as string]" will // be returned. // // NOTE: objAsString is intended to produce human readable output. It is lossy, // and it is not intended to be used for serialization. function objAsString(obj) { // Check for undefined input. if (obj === undefined) { return "[cannot convert undefined to string]"; } if (obj === null) { return "[cannot convert null to string]"; } // Parse the error into a string. if (typeof obj === "string") { return obj; } // Check if the object is an error, and return the message of the error if // so. if (obj instanceof Error) { return obj.message; } // Check if the object has a 'toString' method defined on it. To ensure // that we don't crash or throw, check that the toString is a function, and // also that the return value of toString is a string. if (Object.prototype.hasOwnProperty.call(obj, "toString")) { if (typeof obj.toString === "function") { const str = obj.toString(); if (typeof str === "string") { return str; } } } // If the object does not have a custom toString, attempt to perform a // JSON.stringify. We use a lot of bigints in libskynet, and calling // JSON.stringify on an object with a bigint will cause a throw, so we add // some custom handling to allow bigint objects to still be encoded. try { return JSON.stringify(obj, (_, v) => { if (typeof v === "bigint") { return v.toString(); } return v; }); } catch (err) { if (err !== undefined && typeof err.message === "string") { return `[stringify failed]: ${err.message}`; } return "[stringify failed]"; } } // addContextToErr is a helper function that standardizes the formatting of // adding context to an error. // // NOTE: To protect against accidental situations where an Error type or some // other type is provided instead of a string, we wrap both of the inputs with // objAsString before returning them. This prevents runtime failures. function addContextToErr$1(err, context) { if (err === null || err === undefined) { err = "[no error provided]"; } return objAsString(context) + ": " + objAsString(err); } const MAX_UINT_64 = 18446744073709551615n; // bufToB64 will convert a Uint8Array to a base64 string with URL encoding and // no padding characters. function bufToB64$1(buf) { const b64Str = btoa(String.fromCharCode(...buf)); return b64Str.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); } // encodeU64 will encode a bigint in the range of a uint64 to an 8 byte // Uint8Array. function encodeU64$1(num) { // Check the bounds on the bigint. if (num < 0) { return [new Uint8Array(0), "expected a positive integer"]; } if (num > MAX_UINT_64) { return [new Uint8Array(0), "expected a number no larger than a uint64"]; } // Encode the bigint into a Uint8Array. const encoded = new Uint8Array(8); for (let i = 0; i < encoded.length; i++) { const byte = Number(num & 0xffn); encoded[i] = byte; num = num >> 8n; } return [encoded, null]; } const gfi$1 = function (init) { let i; const r = new Float64Array(16); if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; return r; }; gfi$1([1]); gfi$1([ 0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203, ]); gfi$1([ 0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406, ]); gfi$1([ 0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169, ]); gfi$1([ 0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, ]); gfi$1([ 0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83, ]); // checkObj take an untrusted object and a list of typechecks to perform and // will check that the object adheres to the typechecks. If a type is missing // or has the wrong type, an error will be returned. This is intended to be // used to check untrusted objects after they get decoded from JSON. This is // particularly useful when receiving objects from untrusted entities over the // network or over postMessage. // // Below is an example object, followed by the call that you would make to // checkObj to verify the object. // // const expectedObj = { // aNum: 35, // aStr: "hi", // aBig: 10n, // }; // // const err = checkObj(expectedObj, [ // ["aNum", "number"], // ["aStr", "string"], // ["aBig", "bigint"], // ]); function checkObj(obj, checks) { for (let i = 0; i < checks.length; i++) { const check = checks[i]; const type = typeof obj[check[0]]; if (type !== check[1]) { return "check failed, expecting " + check[1] + " got " + type; } } return null; } // Create the queryMap. let queries = {}; // Define the nonce handling. nonceSeed is 16 random bytes that get generated // at init and serve as the baseline for creating random nonces. nonceCounter // tracks which messages have been sent. We hash together the nonceSeed and the // current nonceCounter to get a secure nonce. // // We need a secure nonce so that we know which messages from the kernel are // intended for us. There could be multiple pieces of independent code talking // to the kernel and using nonces, by having secure random nonces we can // guarantee that the applications will not use conflicting nonces. let nonceSeed; let nonceCounter; function initNonce() { nonceSeed = new Uint8Array(16); nonceCounter = 0; crypto.getRandomValues(nonceSeed); } // nextNonce will combine the nonceCounter with the nonceSeed to produce a // unique string that can be used as the nonce with the kernel. // // Note: the nonce is only ever going to be visible to the kernel and to other // code running in the same webpage, so we don't need to hash our nonceSeed. We // just need it to be unique, not undetectable. function nextNonce() { let nonceNum = nonceCounter; nonceCounter += 1; let [nonceNumBytes, err] = encodeU64$1(BigInt(nonceNum)); if (err !== null) { // encodeU64 only fails if nonceNum is outside the bounds of a // uint64, which shouldn't happen ever. logErr("encodeU64 somehow failed", err); } let noncePreimage = new Uint8Array(nonceNumBytes.length + nonceSeed.length); noncePreimage.set(nonceNumBytes, 0); noncePreimage.set(nonceSeed, nonceNumBytes.length); return bufToB64$1(noncePreimage); } // Establish the handler for incoming messages. function handleMessage(event) { // Ignore all messages that aren't from approved kernel sources. The two // approved sources are skt.us and the browser extension bridge (which has // an event.source equal to 'window') if (event.source !== window && event.origin !== "https://skt.us") { return; } // Ignore any messages that don't have a method and data field. if (!("method" in event.data) || !("data" in event.data)) { return; } // Handle logging messages. if (event.data.method === "log") { // We display the logging message if the kernel is a browser // extension, so that the kernel's logs appear in the app // console as well as the extension console. If the kernel is // in an iframe, its logging messages will already be in the // app console and therefore don't need to be displayed. if (kernelOrigin === window.origin) { if (event.data.data.isErr) { console.error(event.data.data.message); } else { console.log(event.data.data.message); } } return; } // init is complete when the kernel sends us the auth status. If the // user is logged in, report success, otherwise return an error // indicating that the user is not logged in. if (event.data.method === "kernelAuthStatus") { // If we have received an auth status message, it means the bootloader // at a minimum is working. if (initResolved === false) { initResolved = true; // We can't actually establish that init is complete until the // kernel source has been set. This happens async and might happen // after we receive the auth message. sourcePromise.then(() => { initResolve(); }); } // If the auth status message says that login is complete, it means // that the user is logged in. if (loginResolved === false && event.data.data.loginComplete === true) { loginResolved = true; loginResolve(); } // If the auth status message says that the kernel loaded, it means // that the kernel is ready to receive messages. if (kernelLoadedResolved === false && event.data.data.kernelLoaded !== "not yet") { kernelLoadedResolved = true; if (event.data.data.kernelLoaded === "success") { kernelLoadedResolve(null); } else { kernelLoadedResolve(event.data.data.kernelLoaded); } } // If we have received a message indicating that the user has logged // out, we need to reload the page and reset the auth process. if (event.data.data.logoutComplete === true) { { logoutResolve(); } window.location.reload(); } return; } // Check that the message sent has a nonce. We don't log // on failure because the message may have come from 'window', which // will happen if the app has other messages being sent to the window. if (!("nonce" in event.data)) { return; } // If we can't locate the nonce in the queries map, there is nothing to do. // This can happen especially for responseUpdate messages. if (!(event.data.nonce in queries)) { return; } let query = queries[event.data.nonce]; // Handle a response. Once the response has been received, it is safe to // delete the query from the queries map. if (event.data.method === "response") { queries[event.data.nonce].resolve([event.data.data, event.data.err]); delete queries[event.data.nonce]; return; } // Handle a response update. if (event.data.method === "responseUpdate") { // If no update handler was provided, there is nothing to do. if (typeof query.receiveUpdate === "function") { query.receiveUpdate(event.data.data); } return; } // Handle a responseNonce. if (event.data.method === "responseNonce") { if (typeof query.kernelNonceReceived === "function") { query.kernelNonceReceived(event.data.data.nonce); } return; } // Ignore any other messages as they might be from other applications. } // launchKernelFrame will launch the skt.us iframe that is used to connect to the // Skynet kernel if the kernel cannot be reached through the browser extension. function launchKernelFrame() { let iframe = document.createElement("iframe"); iframe.src = "https://skt.us"; iframe.width = "0"; iframe.height = "0"; iframe.style.border = "0"; iframe.style.position = "absolute"; document.body.appendChild(iframe); kernelSource = iframe.contentWindow; kernelOrigin = "https://skt.us"; kernelAuthLocation = "https://skt.us/auth.html"; sourceResolve(); // Set a timer to fail the login process if the kernel doesn't load in // time. setTimeout(() => { if (initResolved === true) { return; } initResolved = true; initResolve("tried to open kernel in iframe, but hit a timeout"); }, 24000); } // messageBridge will send a message to the bridge of the skynet extension to // see if it exists. If it does not respond or if it responds with an error, // messageBridge will open an iframe to skt.us and use that as the kernel. let kernelSource; let kernelOrigin; let kernelAuthLocation; function messageBridge() { // Establish the function that will handle the bridge's response. let bridgeInitComplete = false; let bridgeResolve = () => { }; // Need to set bridgeResolve here to make tsc happy let p = new Promise((resolve) => { bridgeResolve = resolve; }); p.then(([, err]) => { // Check if the timeout already elapsed. if (bridgeInitComplete === true) { logErr("received response from bridge, but init already finished"); return; } bridgeInitComplete = true; // Deconstruct the input and return if there's an error. if (err !== null) { logErr("bridge exists but returned an error", err); launchKernelFrame(); return; } // Bridge has responded successfully, and there's no error. kernelSource = window; kernelOrigin = window.origin; kernelAuthLocation = "http://kernel.skynet/auth.html"; console.log("established connection to bridge, using browser extension for kernel"); sourceResolve(); }); // Add the handler to the queries map. let nonce = nextNonce(); queries[nonce] = { resolve: bridgeResolve, }; // Send a message to the bridge of the browser extension to determine // whether the bridge exists. window.postMessage({ nonce, method: "kernelBridgeVersion", }, window.origin); // Set a timeout, if we do not hear back from the bridge in 500 // milliseconds we assume that the bridge is not available. setTimeout(() => { // If we've already received and processed a message from the // bridge, there is nothing to do. if (bridgeInitComplete === true) { return; } bridgeInitComplete = true; log("browser extension not found, falling back to skt.us"); launchKernelFrame(); }, 500); return initPromise; } // init is a function that returns a promise which will resolve when // initialization is complete. // // The init / auth process has 5 stages. The first stage is that something // somewhere needs to call init(). It is safe to call init() multiple times, // thanks to the 'initialized' variable. let initialized = false; // set to true once 'init()' has been called let initResolved = false; // set to true once we know the bootloader is working let initResolve; let initPromise; let loginResolved = false; // set to true once we know the user is logged in let loginResolve; let loginPromise; let kernelLoadedResolved = false; // set to true once the user kernel is loaded let kernelLoadedResolve; let kernelLoadedPromise; let logoutResolve; let logoutPromise; let sourceResolve; let sourcePromise; // resolves when the source is known and set function init() { // If init has already been called, just return the init promise. if (initialized === true) { return initPromise; } initialized = true; // Run all of the init functions. initNonce(); window.addEventListener("message", handleMessage); messageBridge(); // Create the promises that resolve at various stages of the auth flow. initPromise = new Promise((resolve) => { initResolve = resolve; }); loginPromise = new Promise((resolve) => { loginResolve = resolve; }); kernelLoadedPromise = new Promise((resolve) => { kernelLoadedResolve = resolve; }); logoutPromise = new Promise((resolve) => { logoutResolve = resolve; }); sourcePromise = new Promise((resolve) => { sourceResolve = resolve; }); // Return the initPromise, which will resolve when bootloader init is // complete. return initPromise; } // callModule is a generic function to call a module. The first input is the // module identifier (typically a skylink), the second input is the method // being called on the module, and the final input is optional and contains // input data to be passed to the module. The input data will depend on the // module and the method that is being called. The return value is an ErrTuple // that contains the module's response. The format of the response is an // arbitrary object whose fields depend on the module and method being called. // // callModule can only be used for query-response communication, there is no // support for sending or receiving updates. function callModule(module, method, data) { let moduleCallData = { module, method, data, }; let [, query] = newKernelQuery("moduleCall", moduleCallData, false); return query; } // connectModule is the standard function to send a query to a module that can // optionally send and optionally receive updates. The first three inputs match // the inputs of 'callModule', and the fourth input is a function that will be // called any time that the module sends a responseUpdate. The receiveUpdate // function should have the following signature: // // `function receiveUpdate(data: any)` // // The structure of the data will depend on the module and method that was // queried. // // The first return value is a 'sendUpdate' function that can be called to send // a queryUpdate to the module. The sendUpdate function has the same signature // as the receiveUpdate function, it's an arbitrary object whose fields depend // on the module and method being queried. // // The second return value is a promise that returns an ErrTuple. It will // resolve when the module sends a response message, and works the same as the // return value of callModule. function connectModule(module, method, data, receiveUpdate) { let moduleCallData = { module, method, data, }; return newKernelQuery("moduleCall", moduleCallData, true, receiveUpdate); } // newKernelQuery opens a query to the kernel. Details like postMessage // communication and nonce handling are all abstracted away by newKernelQuery. // // The first arg is the method that is being called on the kernel, and the // second arg is the data that will be sent to the kernel as input to the // method. // // The thrid arg is an optional function that can be passed in to receive // responseUpdates to the query. Not every query will send responseUpdates, and // most responseUpdates can be ignored, but sometimes contain useful // information like download progress. // // The first output is a 'sendUpdate' function that can be called to send a // queryUpdate. The second output is a promise that will resolve when the query // receives a response message. Once the response message has been received, no // more updates can be sent or received. function newKernelQuery(method, data, sendUpdates, receiveUpdate) { // NOTE: The implementation here is gnarly, because I didn't want to use // async/await (that decision should be left to the caller) and I also // wanted this function to work correctly even if init() had not been // called yet. // // This function returns a sendUpdate function along with a promise, so we // can't simply wrap everything in a basic promise. The sendUpdate function // has to block internally until all of the setup is complete, and then we // can't send a query until all of the setup is complete, and the setup // cylce has multiple dependencies and therefore we get a few promises that // all depend on each other. // // Using async/await here actually breaks certain usage patterns (or at // least makes them much more difficult to use correctly). The standard way // to establish duplex communication using connectModule is to define a // variable 'sendUpdate' before defining the function 'receiveUpdate', and // then setting 'sendUpdate' equal to the first return value of // 'connectModue'. It looks like this: // // let sendUpdate; // let receiveUpdate = function(data: any) { // if (data.needsUpdate) { // sendUpdate(someUpdate) // } // } // let [sendUpdateFn, response] = connectModule(x, y, z, receiveUpdate) // sendUpdate = sendUpdateFn // // If we use async/await, it's not safe to set sendUpdate after // connectModule returns because 'receiveUpdate' may be called before // 'sendUpdate' is set. You can fix that by using a promise, but it's a // complicated fix and we want this library to be usable by less // experienced developers. // // Therefore, we make an implementation tradeoff here and avoid async/await // at the cost of having a bunch of complicated promise chaining. // Create a promise that will resolve once the nonce is available. We // cannot get the nonce until init() is complete. getNonce therefore // implies that init is complete. let getNonce = new Promise((resolve) => { init().then(() => { kernelLoadedPromise.then(() => { resolve(nextNonce()); }); }); }); // Two promises are being created at once here. Once is 'p', which will be // returned to the caller of newKernelQuery and will be resolved when the // kernel provides a 'response' message. The other is for internal use and // will resolve once the query has been created. let p; let haveQueryCreated = new Promise((queryCreatedResolve) => { p = new Promise((resolve) => { getNonce.then((nonce) => { queries[nonce] = { resolve }; if (receiveUpdate !== null && receiveUpdate !== undefined) { queries[nonce]["receiveUpdate"] = receiveUpdate; } queryCreatedResolve(nonce); }); }); }); // Create a promise that will be resolved once we are ready to receive the // kernelNonce. We won't be ready to receive the kernel nonce until after // the queries[nonce] object has been created. let readyForKernelNonce; let getReadyForKernelNonce = new Promise((resolve) => { readyForKernelNonce = resolve; }); // Create the sendUpdate function. It defaults to doing nothing. After the // sendUpdate function is ready to receive the kernelNonce, resolve the // promise that blocks until the sendUpdate function is ready to receive // the kernel nonce. let sendUpdate; if (sendUpdates !== true) { sendUpdate = () => { }; readyForKernelNonce(); // We won't get a kernel nonce, no reason to block. } else { // sendUpdate will send an update to the kernel. The update can't be // sent until the kernel nonce is known. Create a promise that will // resolve when the kernel nonce is known. // // This promise cannot itself be created until the queries[nonce] // object has been created, so block for the query to be created. let blockForKernelNonce = new Promise((resolve) => { haveQueryCreated.then((nonce) => { queries[nonce]["kernelNonceReceived"] = resolve; readyForKernelNonce(); }); }); // The sendUpdate function needs both the local nonce and also the // kernel nonce. Block for both. Having the kernel nonce implies that // the local nonce is ready, therefore start by blocking for the kernel // nonce. sendUpdate = function (updateData) { blockForKernelNonce.then((nonce) => { kernelSource.postMessage({ method: "queryUpdate", nonce, data: updateData, }, kernelOrigin); }); }; } // Prepare to send the query to the kernel. The query cannot be sent until // the queries object is created and also we are ready to receive the // kernel nonce. haveQueryCreated.then((nonce) => { getReadyForKernelNonce.then(() => { // There are two types of messages we can send depending on whether // we are talking to skt.us or the background script. let kernelMessage = { method, nonce, data, sendKernelNonce: sendUpdates, }; let backgroundMessage = { method: "newKernelQuery", nonce, data: kernelMessage, }; // The message structure needs to adjust based on whether we are // talking directly to the kernel or whether we are talking to the // background page. if (kernelOrigin === "https://skt.us") { kernelSource.postMessage(kernelMessage, kernelOrigin); } else { kernelSource.postMessage(backgroundMessage, kernelOrigin); } }); }); // Return sendUpdate and the promise. sendUpdate is already set to block // until all the necessary prereqs are complete. return [sendUpdate, p]; } // There are 5 stages of auth. // // Stage 0: Bootloader is not loaded. // Stage 1: Bootloader is loaded, user is not logged in. // Stage 2: Bootloader is loaded, user is logged in. // Stage 3: Kernel is loaded, user is logged in. // Stage 4: Kernel is loaded, user is logged out. // // init() will block until auth has reached stage 1. If the user is already // logged in from a previous session, auth will immediately progress to stage // 2. // // loginComplete() will block until auth has reached stage 2. The kernel is not // ready to receive messages yet, but apps do not need to present users with a // login dialog. // // kernelLoaded() will block until auth has reached stage 3. kernelLoaded() // returns a promise that can resolve with an error. If there was an error, it // means the kernel could not be loaded and cannot be used. // // logoutComplete() will block until auth has reached stage 4. libkernel does // not support resetting the auth stages, once stage 4 has been reached the app // needs to refresh. // loginComplete will resolve when the user has successfully logged in. function loginComplete() { return loginPromise; } // kernelLoaded will resolve when the user has successfully loaded the kernel. // If there was an error in loading the kernel, the error will be returned. // // NOTE: kernelLoaded will not resolve until after loginComplete has resolved. function kernelLoaded() { return kernelLoadedPromise; } // logoutComplete will resolve when the user has logged out. Note that // logoutComplete will only resolve if the user logged in first - if the user // was not logged in to begin with, this promise will not resolve. function logoutComplete() { return logoutPromise; } // openAuthWindow is intended to be used as an onclick target when the user // clicks the 'login' button on a skynet application. It will block until the // auth location is known, and then it will pop open the correct auth window // for the user. // // NOTE: openAuthWindow will only open a window if the user is not already // logged in. If the user is already logged in, this function is a no-op. // // NOTE: When using this function, you probably want to have your login button // faded out or presenting the user with a spinner until init() resolves. In // the worst case (user has no browser extension, and is on a slow internet // connection) this could take multiple seconds. function openAuthWindow() { // openAuthWindow doesn't care what the auth status is, it's just trying to // open the right window. init().then(() => { window.open(kernelAuthLocation, "_blank"); }); } // download will take a skylink and return the file data for that skylink. The // download occurs using a kernel module that verifies the data's integrity and // prevents the portal from lying about the download. function download(skylink) { return new Promise((resolve) => { let downloadModule = "AQCIaQ0P-r6FwPEDq3auCZiuH_jqrHfqRcY7TjZ136Z_Yw"; let data = { skylink, }; callModule(downloadModule, "secureDownload", data).then(([result, err]) => { // Pull the fileData out of the result. if (err !== null) { resolve([new Uint8Array(0), addContextToErr$1(err, "unable to complete download")]); return; } resolve([result.fileData, null]); }); }); } // registryRead will perform a registry read on a portal. readEntry does not // guarantee that the latest revision has been provided, however it does // guarantee that the provided data has a matching signature. // // registryRead returns the full registry entry object provided by the module // because the object is relatively complex and all of the fields are more or // less required. function registryRead(publicKey, dataKey) { return new Promise((resolve) => { let registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q"; let data = { publicKey, dataKey, }; callModule(registryModule, "readEntry", data).then(([result, err]) => { if (err !== null) { resolve([{}, addContextToErr$1(err, "readEntry module call failed")]); return; } resolve([ { exists: result.exists, entryData: result.entryData, revision: result.revision, }, null, ]); }); }); } // registryWrite will perform a registry write on a portal. // // registryWrite is not considered a safe function, there are easy ways to // misuse registryWrite such that user data will be lost. We recommend using a // safe set of functions for writing to the registry such as getsetjson. function registryWrite(keypair, dataKey, entryData, revision) { return new Promise((resolve) => { let registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q"; let callData = { publicKey: keypair.publicKey, secretKey: keypair.secretKey, dataKey, entryData, revision, }; callModule(registryModule, "writeEntry", callData).then(([result, err]) => { if (err !== null) { resolve(["", err]); return; } resolve([result.entryID, null]); }); }); } // upload will take a filename and some file data and perform a secure upload // to Skynet. All data is verified and the correct Skylink is returned. This // function cannot fully guarantee that the data was pinned, but it can fully // guarantee that the final skylink matches the data that was presented for the // upload. function upload(filename, fileData) { return new Promise((resolve) => { // Prepare the module call. let uploadModule = "AQAT_a0MzOInZoJzt1CwBM2U8oQ3GIfP5yKKJu8Un-SfNg"; let data = { filename, fileData, }; callModule(uploadModule, "secureUpload", data).then(([result, err]) => { // Pull the skylink out of the result. if (err !== null) { resolve(["", addContextToErr$1(err, "uable to complete upload")]); return; } resolve([result.skylink, null]); }); }); } // kernelVersion will fetch the version number of the kernel. If successful, // the returned value will be an object containing a field 'version' with a // version string, and a 'distribtion' field with a string that states the // distribution of the kernel". function kernelVersion() { return new Promise((resolve) => { let [, query] = newKernelQuery("version", {}, false); query.then(([result, err]) => { if (err !== null) { resolve(["", "", err]); return; } resolve([result.version, result.distribution, err]); }); }); } var kernel = /*#__PURE__*/Object.freeze({ __proto__: null, kernelLoaded: kernelLoaded, loginComplete: loginComplete, logoutComplete: logoutComplete, openAuthWindow: openAuthWindow, download: download, registryRead: registryRead, registryWrite: registryWrite, upload: upload, kernelVersion: kernelVersion, callModule: callModule, connectModule: connectModule, init: init, newKernelQuery: newKernelQuery, addContextToErr: addContextToErr$1, checkObj: checkObj }); // Blake2B, adapted from the reference implementation in RFC7693 // Ported to Javascript by DC - https://github.com/dcposch // Then ported to typescript by https://github.com/DavidVorick // 64-bit unsigned addition // Sets v[a,a+1] += v[b,b+1] // v should be a Uint32Array function ADD64AA(v, a, b) { const o0 = v[a] + v[b]; let o1 = v[a + 1] + v[b + 1]; if (o0 >= 0x100000000) { o1++; } v[a] = o0; v[a + 1] = o1; } // 64-bit unsigned addition // Sets v[a,a+1] += b // b0 is the low 32 bits of b, b1 represents the high 32 bits function ADD64AC(v, a, b0, b1) { let o0 = v[a] + b0; if (b0 < 0) { o0 += 0x100000000; } let o1 = v[a + 1] + b1; if (o0 >= 0x100000000) { o1++; } v[a] = o0; v[a + 1] = o1; } // Little-endian byte access function B2B_GET32(arr, i) { return arr[i] ^ (arr[i + 1] << 8) ^ (arr[i + 2] << 16) ^ (arr[i + 3] << 24); } // G Mixing function // The ROTRs are inlined for speed function B2B_G(a, b, c, d, ix, iy, m, v) { const x0 = m[ix]; const x1 = m[ix + 1]; const y0 = m[iy]; const y1 = m[iy + 1]; ADD64AA(v, a, b); // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s ADD64AC(v, a, x0, x1); // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits let xor0 = v[d] ^ v[a]; let xor1 = v[d + 1] ^ v[a + 1]; v[d] = xor1; v[d + 1] = xor0; ADD64AA(v, c, d); // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits xor0 = v[b] ^ v[c]; xor1 = v[b + 1] ^ v[c + 1]; v[b] = (xor0 >>> 24) ^ (xor1 << 8); v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8); ADD64AA(v, a, b); ADD64AC(v, a, y0, y1); // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits xor0 = v[d] ^ v[a]; xor1 = v[d + 1] ^ v[a + 1]; v[d] = (xor0 >>> 16) ^ (xor1 << 16); v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16); ADD64AA(v, c, d); // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits xor0 = v[b] ^ v[c]; xor1 = v[b + 1] ^ v[c + 1]; v[b] = (xor1 >>> 31) ^ (xor0 << 1); v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1); } // Initialization Vector const BLAKE2B_IV32 = new Uint32Array([ 0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a, 0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c, 0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19, ]); const SIGMA8 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, ]; // These are offsets into a uint64 buffer. // Multiply them all by 2 to make them offsets into a uint32 buffer, // because this is Javascript and we don't have uint64s const SIGMA82 = new Uint8Array(SIGMA8.map(function (x) { return x * 2; })); // Compression function. 'last' flag indicates last block. // Note we're representing 16 uint64s as 32 uint32s function blake2bCompress(ctx, last) { const v = new Uint32Array(32); const m = new Uint32Array(32); let i = 0; // init work variables for (i = 0; i < 16; i++) { v[i] = ctx.h[i]; v[i + 16] = BLAKE2B_IV32[i]; } // low 64 bits of offset v[24] = v[24] ^ ctx.t; v[25] = v[25] ^ (ctx.t / 0x100000000); // high 64 bits not supported, offset may not be higher than 2**53-1 // last block flag set ? if (last) { v[28] = ~v[28]; v[29] = ~v[29]; } // get little-endian words for (i = 0; i < 32; i++) { m[i] = B2B_GET32(ctx.b, 4 * i); } // twelve rounds of mixing for (i = 0; i < 12; i++) { B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1], m, v); B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3], m, v); B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5], m, v); B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7], m, v); B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9], m, v); B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11], m, v); B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13], m, v); B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15], m, v); } for (i = 0; i < 16; i++) { ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16]; } } // Creates a BLAKE2b hashing context // Requires an output length between 1 and 64 bytes function blake2bInit() { // state, 'param block' const ctx = { b: new Uint8Array(128), h: new Uint32Array(16), t: 0, c: 0, outlen: 32, // output length in bytes }; // initialize hash state for (let i = 0; i < 16; i++) { ctx.h[i] = BLAKE2B_IV32[i]; } ctx.h[0] ^= 0x01010000 ^ 32; return ctx; } // Updates a BLAKE2b streaming hash // Requires hash context and Uint8Array (byte array) function blake2bUpdate(ctx, input) { for (let i = 0; i < input.length; i++) { if (ctx.c === 128) { // buffer full ? ctx.t += ctx.c; // add counters blake2bCompress(ctx, false); // compress (not last) ctx.c = 0; // counter to zero } ctx.b[ctx.c++] = input[i]; } } // Completes a BLAKE2b streaming hash // Returns a Uint8Array containing the message digest function blake2bFinal(ctx) { ctx.t += ctx.c; // mark last block offset while (ctx.c < 128) { // fill up with zeros ctx.b[ctx.c++] = 0; } blake2bCompress(ctx, true); // final block flag = 1 // little endian convert and store const out = new Uint8Array(ctx.outlen); for (let i = 0; i < ctx.outlen; i++) { out[i] = ctx.h[i >> 2] >> (8 * (i & 3)); } return out; } // Computes the blake2b hash of the input. Returns 32 bytes. let blake2b = function (input) { const ctx = blake2bInit(); blake2bUpdate(ctx, input); return blake2bFinal(ctx); }; const defaultPortalList = ["https://siasky.net", "https://web3portal.com"]; // DICTIONARY_UNIQUE_PREFIX defines the number of characters that are // guaranteed to be unique for each word in the dictionary. The seed code only // looks at these three characters when parsing a word, allowing users to make // substitutions for words if they prefer or find it easier to memorize. const DICTIONARY_UNIQUE_PREFIX = 3; // dictionary contains the word list for the mysky seed. const dictionary = [ "abbey", "ablaze", "abort", "absorb", "abyss", "aces", "aching", "acidic", "across", "acumen", "adapt", "adept", "adjust", "adopt", "adult", "aerial", "afar", "affair", "afield", "afloat", "afoot", "afraid", "after", "agenda", "agile", "aglow", "agony", "agreed", "ahead", "aided", "aisle", "ajar", "akin", "alarms", "album", "alerts", "alley", "almost", "aloof", "alpine", "also", "alumni", "always", "amaze", "ambush", "amidst", "ammo", "among", "amply", "amused", "anchor", "angled", "ankle", "antics", "anvil", "apart", "apex", "aphid", "aplomb", "apply", "archer", "ardent", "arena", "argue", "arises", "army", "around", "arrow", "ascend", "aside", "asked", "asleep", "aspire", "asylum", "atlas", "atom", "atrium", "attire", "auburn", "audio", "august", "aunt", "autumn", "avatar", "avidly", "avoid", "awful", "awning", "awoken", "axes", "axis", "axle", "aztec", "azure", "baby", "bacon", "badge", "bailed", "bakery", "bamboo", "banjo", "basin", "batch", "bawled", "bays", "beer", "befit", "begun", "behind", "being", "below", "bested", "bevel", "beware", "beyond", "bias", "bids", "bikini", "birth", "bite", "blip", "boat", "bodies", "bogeys", "boil", "boldly", "bomb", "border", "boss", "both", "bovine", "boxes", "broken", "brunt", "bubble", "budget", "buffet", "bugs", "bulb", "bumper", "bunch", "butter", "buying", "buzzer", "byline", "bypass", "cabin", "cactus", "cadets", "cafe", "cage", "cajun", "cake", "camp", "candy", "casket", "catch", "cause", "cease", "cedar", "cell", "cement", "cent", "chrome", "cider", "cigar", "cinema", "circle", "claim", "click", "clue", "coal", "cobra", "cocoa", "code", "coffee", "cogs", "coils", "colony", "comb", "cool", "copy", "cousin", "cowl", "cube", "cuffs", "custom", "dads", "daft", "dagger", "daily", "damp", "dapper", "darted", "dash", "dating", "dawn", "dazed", "debut", "decay", "deftly", "deity", "dented", "depth", "desk", "devoid", "dice", "diet", "digit", "dilute", "dime", "dinner", "diode", "ditch", "divers", "dizzy", "doctor", "dodge", "does", "dogs", "doing", "donuts", "dosage", "dotted", "double", "dove", "down", "dozen", "dreams", "drinks", "drunk", "drying", "dual", "dubbed", "dude", "duets", "duke", "dummy", "dunes", "duplex", "dusted", "duties", "dwarf", "dwelt", "dying", "each", "eagle", "earth", "easy", "eating", "echo", "eden", "edgy", "edited", "eels", "eggs", "eight", "either", "eject", "elapse", "elbow", "eldest", "eleven", "elite", "elope", "else", "eluded", "emails", "ember", "emerge", "emit", "empty", "energy", "enigma", "enjoy", "enlist", "enmity", "enough", "ensign", "envy", "epoxy", "equip", "erase", "error", "estate", "etched", "ethics", "excess", "exhale", "exit", "exotic", "extra", "exult", "fading", "faked", "fall", "family", "fancy", "fatal", "faulty", "fawns", "faxed", "fazed", "feast", "feel", "feline", "fences", "ferry", "fever", "fewest", "fiat", "fibula", "fidget", "fierce", "fight", "films", "firm", "five", "fixate", "fizzle", "fleet", "flying", "foamy", "focus", "foes", "foggy", "foiled", "fonts", "fossil", "fowls", "foxes", "foyer", "framed", "frown", "fruit", "frying", "fudge", "fuel", "fully", "fuming", "fungal", "future", "fuzzy", "gables", "gadget", "gags", "gained", "galaxy", "gambit", "gang", "gasp", "gather", "gauze", "gave", "gawk", "gaze", "gecko", "geek", "gels", "germs", "geyser", "ghetto", "ghost", "giant", "giddy", "gifts", "gills", "ginger", "girth", "giving", "glass", "glide", "gnaw", "gnome", "goat", "goblet", "goes", "going", "gone", "gopher", "gossip", "gotten", "gown", "grunt", "guest", "guide", "gulp", "guru", "gusts", "gutter", "guys", "gypsy", "gyrate", "hairy", "having", "hawk", "hazard", "heels", "hefty", "height", "hence", "heron", "hiding", "hijack", "hiker", "hills", "hinder", "hippo", "hire", "hive", "hoax", "hobby", "hockey", "hold", "honked", "hookup", "hope", "hornet", "hotel", "hover", "howls", "huddle", "huge", "hull", "humid", "hunter", "huts", "hybrid", "hyper", "icing", "icon", "idiom", "idled", "idols", "igloo", "ignore", "iguana", "impel", "incur", "injury", "inline", "inmate", "input", "insult", "invoke", "ionic", "irate", "iris", "irony", "island", "issued", "itches", "items", "itself", "ivory", "jabbed", "jaded", "jagged", "jailed", "jargon", "jaunt", "jaws", "jazz", "jeans", "jeers", "jester", "jewels", "jigsaw", "jingle", "jive", "jobs", "jockey", "jogger", "joking", "jolted", "jostle", "joyous", "judge", "juicy", "july", "jump", "junk", "jury", "karate", "keep", "kennel", "kept", "kettle", "king", "kiosk", "kisses", "kiwi", "knee", "knife", "koala", "ladder", "lagoon", "lair", "lakes", "lamb", "laptop", "large", "last", "later", "lava", "layout", "lazy", "ledge", "leech", "left", "legion", "lemon", "lesson", "liar", "licks", "lids", "lied", "light", "lilac", "limits", "linen", "lion", "liquid", "listen", "lively", "loaded", "locker", "lodge", "lofty", "logic", "long", "lopped", "losing", "loudly", "love", "lower", "loyal", "lucky", "lumber", "lunar", "lurk", "lush", "luxury", "lymph", "lynx", "lyrics", "macro", "mailed", "major", "makeup", "malady", "mammal", "maps", "match", "maul", "mayor", "maze", "meant", "memoir", "menu", "merger", "mesh", "metro", "mews", "mice", "midst", "mighty", "mime", "mirror", "misery", "moat", "mobile", "mocked", "mohawk", "molten", "moment", "money", "moon", "mops", "morsel", "mostly", "mouth", "mowing", "much", "muddy", "muffin", "mugged", "mullet", "mumble", "muppet", "mural", "muzzle", "myriad", "myth", "nagged", "nail", "names", "nanny", "napkin", "nasty", "navy", "nearby", "needed", "neon", "nephew", "nerves", "nestle", "never", "newt", "nexus", "nibs", "niche", "niece", "nifty", "nimbly", "nobody", "nodes", "noises", "nomad", "noted", "nouns", "nozzle", "nuance", "nudged", "nugget", "null", "number", "nuns", "nurse", "nylon", "oaks", "oars", "oasis", "object", "occur", "ocean", "odds", "offend", "often", "okay", "older", "olive", "omega", "onion", "online", "onto", "onward", "oozed", "opened", "opus", "orange", "orbit", "orchid", "orders", "organs", "origin", "oscar", "otter", "ouch", "ought", "ounce", "oust", "oval", "oven", "owed", "owls", "owner", "oxygen", "oyster", "ozone", "pact", "pager", "palace", "paper", "pastry", "patio", "pause", "peeled", "pegs", "pencil", "people", "pepper", "pests", "petals", "phase", "phone", "piano", "picked", "pierce", "pimple", "pirate", "pivot", "pixels", "pizza", "pledge", "pliers", "plus", "poetry", "point", "poker", "polar", "ponies", "pool", "potato", "pouch", "powder", "pram", "pride", "pruned", "prying", "public", "puck", "puddle", "puffin", "pulp", "punch", "puppy", "purged", "push", "putty", "pylons", "python", "queen", "quick", "quote", "radar", "rafts", "rage", "raking", "rally", "ramped", "rapid", "rarest", "rash", "rated", "ravine", "rays", "razor", "react", "rebel", "recipe", "reduce", "reef", "refer", "reheat", "relic", "remedy", "repent", "reruns", "rest", "return", "revamp", "rewind", "rhino", "rhythm", "ribbon", "richly", "ridges", "rift", "rigid", "rims", "riots", "ripped", "rising", "ritual", "river", "roared", "robot", "rodent", "rogue", "roles", "roomy", "roped", "roster", "rotate", "rover", "royal", "ruby", "rudely", "rugged", "ruined", "ruling", "rumble", "runway", "rural", "sack", "safety", "saga", "sailor", "sake", "salads", "sample", "sanity", "sash", "satin", "saved", "scenic", "school", "scoop", "scrub", "scuba", "second", "sedan", "seeded", "setup", "sewage", "sieve", "silk", "sipped", "siren", "sizes", "skater", "skew", "skulls", "slid", "slower", "slug", "smash", "smog", "snake", "sneeze", "sniff", "snout", "snug", "soapy", "sober", "soccer", "soda", "soggy", "soil", "solved", "sonic", "soothe", "sorry", "sowed", "soya", "space", "speedy", "sphere", "spout", "sprig", "spud", "spying", "square", "stick", "subtly", "suede", "sugar", "summon", "sunken", "surfer", "sushi", "suture", "swept", "sword", "swung", "system", "taboo", "tacit", "tagged", "tail", "taken", "talent", "tamper", "tanks", "tasked", "tattoo", "taunts", "tavern", "tawny", "taxi", "tell", "tender", "tepid", "tether", "thaw", "thorn", "thumbs", "thwart", "ticket", "tidy", "tiers", "tiger", "tilt", "timber", "tinted", "tipsy", "tirade", "tissue", "titans", "today", "toffee", "toilet", "token", "tonic", "topic", "torch", "tossed", "total", "touchy", "towel", "toxic", "toyed", "trash", "trendy", "tribal", "truth", "trying", "tubes", "tucks", "tudor", "tufts", "tugs", "tulips", "tunnel", "turnip", "tusks", "tutor", "tuxedo", "twang", "twice", "tycoon", "typist", "tyrant", "ugly", "ulcers", "umpire", "uncle", "under", "uneven", "unfit", "union", "unmask", "unrest", "unsafe", "until", "unveil", "unwind", "unzip", "upbeat", "update", "uphill", "upkeep", "upload", "upon", "upper", "urban", "urgent", "usage", "useful", "usher", "using", "usual", "utmost", "utopia", "vague", "vain", "value", "vane", "vary", "vats", "vaults", "vector", "veered", "vegan", "vein", "velvet", "vessel", "vexed", "vials", "victim", "video", "viking", "violin", "vipers", "vitals", "vivid", "vixen", "vocal", "vogue", "voice", "vortex", "voted", "vowels", "voyage", "wade", "waffle", "waist", "waking", "wanted", "warped", "water", "waxing", "wedge", "weird", "went", "wept", "were", "whale", "when", "whole", "width", "wield", "wife", "wiggle", "wildly", "winter", "wiring", "wise", "wives", "wizard", "wobbly", "woes", "woken", "wolf", "woozy", "worry", "woven", "wrap", "wrist", "wrong", "yacht", "yahoo", "yanks", ]; // tryStringify will try to turn the provided input into a string. If the input // object is already a string, the input object will be returned. If the input // object has a toString method, the toString method will be called. If that // fails, we try to call JSON.stringify on the object. And if that fails, we // set the return value to "[stringify failed]". function tryStringify(obj) { // Check for undefined input. if (obj === undefined) { return "[cannot stringify undefined input]"; } if (obj === null) { return "[null]"; } // Parse the error into a string. if (typeof obj === "string") { return obj; } // Check if the object has a 'toString' method defined on it. To ensure // that we don't crash or throw, check that the toString is a function, and // also that the return value of toString is a string. if (Object.prototype.hasOwnProperty.call(obj, "toString")) { if (typeof obj.toString === "function") { let str = obj.toString(); if (typeof str === "string") { return str; } } } // If the object does not have a custom toString, attempt to perform a // JSON.stringify. try { return JSON.stringify(obj); } catch { return "[stringify failed]"; } } // addContextToErr is a helper function that standardizes the formatting of // adding context to an error. Within the world of go we discovered that being // persistent about layering context onto errors is helpful when debugging, // even though it often creates rather verbose error messages. // // addContextToErr will return null if the input err is null. // // NOTE: To protect against accidental situations where an Error type or some // other type is provided instead of a string, we wrap both of the inputs with // tryStringify before returning them. This prevents runtime failures. function addContextToErr(err, context) { if (err === null) { err = "[no error provided]"; } err = tryStringify(err); return tryStringify(context) + ": " + tryStringify(err); } // composeErr takes a series of inputs and composes them into a single string. // Each element will be separated by a newline. If the input is not a string, // it will be transformed into a string with JSON.stringify. // // Any object that cannot be stringified will be skipped, though an error will // be logged. function composeErr(...inputs) { let result = ""; let resultEmpty = true; for (let i = 0; i < inputs.length; i++) { if (inputs[i] === null) { continue; } if (resultEmpty) { resultEmpty = false; } else { result += "\n"; } result += tryStringify(inputs[i]); } if (resultEmpty) { return null; } return result; } // Helper consts to make it easy to return empty values alongside errors. const nu8$6 = new Uint8Array(0); // b64ToBuf will take an untrusted base64 string and convert it into a // Uin8Array, returning an error if the input is not valid base64. function b64ToBuf(b64) { // Check that the final string is valid base64. let b64regex = /^[0-9a-zA-Z-_/+=]*$/; if (!b64regex.test(b64)) { return [nu8$6, "provided string is not valid base64"]; } // Swap any '-' characters for '+', and swap any '_' characters for '/' // for use in the atob function. b64 = b64.replace(/-/g, "+").replace(/_/g, "/"); // Perform the conversion. let binStr = atob(b64); let len = binStr.length; let buf = new Uint8Array(len); for (let i = 0; i < len; i++) { buf[i] = binStr.charCodeAt(i); } return [buf, null]; } // bufToHex takes a Uint8Array as input and returns the hex encoding of those // bytes as a string. function bufToHex(buf) { return [...buf].map((x) => x.toString(16).padStart(2, "0")).join(""); } // bufToB64 will convert a Uint8Array to a base64 string with URL encoding and // no padding characters. function bufToB64(buf) { let b64Str = btoa(String.fromCharCode.apply(null, buf)); return b64Str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } // bufToStr takes an ArrayBuffer as input and returns a text string. bufToStr // will check for invalid characters. function bufToStr(buf) { try { let text = new TextDecoder("utf-8", { fatal: true }).decode(buf); return [text, null]; } catch (err) { return ["", addContextToErr(err.toString(), "unable to decode ArrayBuffer to string")]; } } // decodeBigint will take an 8 byte Uint8Array and decode it as a bigint. function decodeBigint(buf) { if (buf.length !== 8) { return [0n, "a number is expected to be 8 bytes"]; } let num = 0n; for (let i = 7; i >= 0; i--) { num *= 256n; num += BigInt(buf[i]); } return [num, null]; } // encodePrefixedBytes takes a Uint8Array as input and returns a Uint8Array // that has the length prefixed as an 8 byte prefix. The input can be at most 4 // GiB. function encodePrefixedBytes(bytes) { let len = bytes.length; if (len > 4294968295) { return [nu8$6, "input is too large to be encoded"]; } let buf = new ArrayBuffer(8 + len); let view = new DataView(buf); view.setUint32(0, len, true); let uint8Bytes = new Uint8Array(buf); uint8Bytes.set(bytes, 8); return [uint8Bytes, null]; } // encodeU64 will encode a bigint in the range of a uint64 to an 8 byte // Uint8Array. function encodeU64(num) { // Check the bounds on the bigint. if (num < 0) { return [nu8$6, "expected a positive integer"]; } if (num > 18446744073709551615n) { return [nu8$6, "expected a number no larger than a uint64"]; } // Encode the bigint into a Uint8Array. let encoded = new Uint8Array(8); for (let i = 0; i < encoded.length; i++) { let byte = Number(num & 0xffn); encoded[i] = byte; num = num >> 8n; } return [encoded, null]; } // hexToBuf takes an untrusted string as input, verifies that the string is // valid hex, and then converts the string to a Uint8Array. function hexToBuf(hex) { // Check that the length makes sense. if (hex.length % 2 != 0) { return [nu8$6, "input has incorrect length"]; } // Check that all of the characters are legal. let match = /[0-9A-Fa-f]*/g; if (!match.test(hex)) { return [nu8$6, "input has invalid character"]; } // Create the buffer and fill it. let matches = hex.match(/.{1,2}/g); if (matches === null) { return [nu8$6, "input is incomplete"]; } let u8 = new Uint8Array(matches.map((byte) => parseInt(byte, 16))); return [u8, null]; } // Helper values for cleanly returning errors. const nu8$5 = new Uint8Array(0); // blake2bAddSubtreeToProofStack will add a subtree to a proof stack. function blake2bAddSubtreeToProofStack(ps, subtreeRoot, subtreeHeight) { // Input checking. if (subtreeRoot.length !== 32) { return "cannot add subtree because root is wrong length"; } // If the proofStack has no elements in it yet, add the subtree // with no further checks. if (ps.subtreeRoots.length === 0) { ps.subtreeRoots.push(subtreeRoot); ps.subtreeHeights.push(subtreeHeight); return null; } // Check the height of the new subtree against the height of the smallest // subtree in the proofStack. If the new subtree is larger, the subtree // cannot be added. let maxHeight = ps.subtreeHeights[ps.subtreeHeights.length - 1]; if (subtreeHeight > maxHeight) { return `cannot add a subtree that is taller ${subtreeHeight} than the smallest ${maxHeight} subtree in the stack`; } // If the new subtreeHeight is smaller than the max height, we can just // append the subtree height without doing anything more. if (subtreeHeight < maxHeight) { ps.subtreeRoots.push(subtreeRoot); ps.subtreeHeights.push(subtreeHeight); return null; } // If the new subtree is the same height as the smallest subtree, we // have to pull the smallest subtree out, combine it with the new // subtree, and push the result. let oldSTR = ps.subtreeRoots.pop(); ps.subtreeHeights.pop(); // We already have the height. let combinedRoot = new Uint8Array(65); combinedRoot[0] = 1; combinedRoot.set(oldSTR, 1); combinedRoot.set(subtreeRoot, 33); let newSubtreeRoot = blake2b(combinedRoot); return blake2bAddSubtreeToProofStack(ps, newSubtreeRoot, subtreeHeight + 1n); } // blake2bAddLeafBytesToProofStack will add a leaf to a proof stack. function blake2bAddLeafBytesToProofStack(ps, leafBytes) { if (leafBytes.length !== 64) { return "proofStack expects leafByte objects to be exactly 64 bytes"; } let taggedBytes = new Uint8Array(65); taggedBytes.set(leafBytes, 1); let subtreeRoot = blake2b(taggedBytes); return blake2bAddSubtreeToProofStack(ps, subtreeRoot, 1n); } // blake2bProofStackRoot returns the final Merkle root of the data in the // current proof stack. function blake2bProofStackRoot(ps) { // Input checking. if (ps.subtreeRoots.length === 0) { return [nu8$5, "cannot compute the Merkle root of an empty data set"]; } // Algorithm is pretty basic, start with the final tree, and then add // it to the previous tree. Repeat until there are no more trees. let baseSubtreeRoot = ps.subtreeRoots.pop(); while (ps.subtreeRoots.length !== 0) { let nextSubtreeRoot = ps.subtreeRoots.pop(); let combinedRoot = new Uint8Array(65); combinedRoot[0] = 1; combinedRoot.set(baseSubtreeRoot, 1); combinedRoot.set(nextSubtreeRoot, 33); baseSubtreeRoot = blake2b(combinedRoot); } return [baseSubtreeRoot, null]; } // nextSubtreeHeight returns the height of the largest subtree that contains // 'start', contains no elements prior to 'start', and also does not contain // 'end'. function nextSubtreeHeight(start, end) { // Input checking. if (end <= start) { return [0n, 0n, `end (${end}) must be strictly larger than start (${start})`]; } // Merkle trees have a nice mathematical property that the largest tree // which contains a particular node and no nodes prior to it will have // a height that is equal to the number of trailing zeroes in the base // 2 representation of the index of that node. // // We are exploiting that property to compute the 'idealTreeHeight'. If // 'start' is zero, the ideal tree height will just keep counting up // forever, so we cut it off at 53. let idealTreeHeight = 1n; let idealTreeSize = 1n; // The conditional inside the loop tests if the next ideal tree size is // acceptable. If it is, we increment the height and double the size. while (start % (idealTreeSize * 2n) === 0n) { idealTreeHeight++; idealTreeSize = idealTreeSize * 2n; } // To compute the max tree height, we essentially just find the largest // power of 2 that is smaller than or equal to the gap between start // and end. let maxTreeHeight = 1n; let maxTreeSize = 1n; let range = end - start + 1n; while (maxTreeSize * 2n < range) { maxTreeHeight++; maxTreeSize = maxTreeSize * 2n; } // Return the smaller of the ideal height and the max height, as each // of them is an upper bound on how large things are allowed to be. if (idealTreeHeight < maxTreeHeight) { return [idealTreeHeight, idealTreeSize, null]; } return [maxTreeHeight, maxTreeSize, null]; } // blake2bMerkleRoot computes the merkle root of the provided data using a leaf // size of 64 bytes and blake2b as the hashing function. function blake2bMerkleRoot(data) { // Check that the input is an acceptable length. if (data.length % 64 !== 0) { return [nu8$5, "cannot take the merkle root of data that is not a multiple of 64 bytes"]; } // Compute the Merkle root. let ps = { subtreeRoots: [], subtreeHeights: [], }; for (let i = 0; i < data.length; i += 64) { blake2bAddLeafBytesToProofStack(ps, data.slice(i, i + 64)); } return blake2bProofStackRoot(ps); } // blake2bVerifySectorRangeProof will verify a merkle proof that the provided // data exists within the provided sector at the provided range. // // NOTE: This implementation only handles a single range, but the transition to // doing mulit-range proofs is not very large. The main reason I didn't extend // this function was because it made the inputs a lot messier. The Sia merkle // tree repo uses the same techniques and has the full implementation, use that // as a reference if you need to extend this function to support multi-range // proofs. function blake2bVerifySectorRangeProof(root, data, rangeStart, rangeEnd, proof) { // Verify the inputs. if (root.length !== 32) { return "provided root is not a blake2b sector root"; } if (rangeEnd <= rangeStart) { return "provided has no data"; } if (rangeStart < 0n) { return "cannot use negative ranges"; } if (rangeEnd > 4194304n) { return "range is out of bounds"; } if (proof.length % 32 !== 0) { return "merkle proof has invalid length"; } if (data.length !== Number(rangeEnd - rangeStart)) { return "data length does not match provided range"; } if (data.length % 64 !== 0) { return "data must have a multiple of 64 bytes"; } // We will consume proof elements until we get to the rangeStart of the // data. let ps = { subtreeRoots: [], subtreeHeights: [], }; let currentOffset = 0n; let proofOffset = 0; while (currentOffset < rangeStart) { if (proof.length < proofOffset + 32) { return "merkle proof has insufficient data"; } let [height, size, errNST] = nextSubtreeHeight(currentOffset / 64n, rangeStart / 64n); if (errNST !== null) { return addContextToErr(errNST, "error computing subtree height of initial proof stack"); } let newSubtreeRoot = new Uint8Array(32); newSubtreeRoot.set(proof.slice(proofOffset, proofOffset + 32), 0); proofOffset += 32; let errSPS = blake2bAddSubtreeToProofStack(ps, newSubtreeRoot, height); if (errSPS !== null) { return addContextToErr(errSPS, "error adding subtree to initial proof stack"); } currentOffset += size * 64n; } // We will consume data elements until we get to the end of the data. let dataOffset = 0; while (data.length > dataOffset) { let errLBPS = blake2bAddLeafBytesToProofStack(ps, data.slice(dataOffset, dataOffset + 64)); if (errLBPS !== null) { return addContextToErr(errLBPS, "error adding leaves to proof stack"); } dataOffset += 64; currentOffset += 64n; } // Consume proof elements until the entire sector is proven. let sectorEnd = 4194304n; while (currentOffset < sectorEnd) { if (proof.length < proofOffset + 32) { return "merkle proof has insufficient data"; } let [height, size, errNST] = nextSubtreeHeight(currentOffset / 64n, sectorEnd / 64n); if (errNST !== null) { return addContextToErr(errNST, "error computing subtree height of trailing proof stack"); } let newSubtreeRoot = new Uint8Array(32); newSubtreeRoot.set(proof.slice(proofOffset, proofOffset + 32), 0); proofOffset += 32; let errSPS = blake2bAddSubtreeToProofStack(ps, newSubtreeRoot, height); if (errSPS !== null) { return addContextToErr(errSPS, "error adding subtree to trailing proof stack"); } currentOffset += size * 64n; } return null; } // Helper consts to make it easier to return empty values in the event of an // error. const nu8$4 = new Uint8Array(0); // verifyDownload will verify a download response from a portal. The input is // essentially components of a skylink - the offset, length, and merkle root. // The output is the raw file data. // // The 'buf' input should match the standard response body of a verified // download request to a portal, which is the skylink raw data followed by a // merkle proof. The offset and length provided as input indicate the offset // and length of the skylink raw data - not the offset and length of the // request within the file (that would be a different set of params). // // The skylink raw data itself breaks down into a metadata component and a file // component. The metadata component will contain information like the length // of the real file, and any fanout structure for large files. The first step // we need to take is verifying the Merkel proof, which will appear at the end // of the buffer. We'll have to hash the data we received and then compare it // against the Merkle proof and ensure it matches the data we are expecting. // Then we'll have to look at the layout to figure out which pieces of the data // are the full file, while also checking for corruption as the file can be // malicious independent of the portal operator. // // As long as the Merkle proof matches the root, offset, and length that we // have as input, the portal is considered non-malicious. Any additional errors // we find after that can be considered malice or incompetence on the part of // the person who uploaded the file. function verifyDownload(root, offset, fetchSize, buf) { let u8 = new Uint8Array(buf); // Input checking. If any of this is incorrect, its safe to blame the // server because the skylink format fundamentally should enable these // to be correct. if (u8.length < fetchSize) { return [nu8$4, true, "provided data is not large enough to cover fetchSize"]; } if (u8.length < 99) { return [nu8$4, true, "provided data is not large enough to contain a skyfile"]; } // Grab the skylinkData and Merkle proof from the array, and then // verify the Merkle proof. let skylinkData = u8.slice(0, Number(fetchSize)); let merkleProof = u8.slice(Number(fetchSize), u8.length); let errVBSRP = blake2bVerifySectorRangeProof(root, skylinkData, offset, fetchSize, merkleProof); if (errVBSRP !== null) { return [nu8$4, true, addContextToErr(errVBSRP, "provided Merkle proof is not valid")]; } // Up until this point, an error indicated that the portal was at fault for // either returning the wrong data or otherwise providing a malformed // repsonse. The remaining checks relate to the consistency of the file // itself, if the file is corrupt but the hash matches, there will be an // error and the portal will not be at fault. // The organization of the skylinkData is always: // layoutBytes || fanoutBytes || metadataBytes || fileBytes // // The layout is always exactly 99 bytes. Bytes [1,8] of the layout // contain the exact size of the fileBytes. Bytes [9, 16] of the layout // contain the exact size of the metadata. And bytes [17,24] of the // layout contain the exact size of the fanout. To get the offset of // the fileData, we need to extract the sizes of the metadata and // fanout, and then add those values to 99 to get the fileData offset. let fileSizeBytes = skylinkData.slice(1, 9); let mdSizeBytes = skylinkData.slice(9, 17); let fanoutSizeBytes = skylinkData.slice(17, 25); let [fileSize, errFSDN] = decodeBigint(fileSizeBytes); if (errFSDN !== null) { return [nu8$4, false, addContextToErr(errFSDN, "unable to decode filesize")]; } let [mdSize, errMDDN] = decodeBigint(mdSizeBytes); if (errMDDN !== null) { return [nu8$4, false, addContextToErr(errMDDN, "unable to decode metadata size")]; } let [fanoutSize, errFODN] = decodeBigint(fanoutSizeBytes); if (errFODN !== null) { return [nu8$4, false, addContextToErr(errFODN, "unable to decode fanout size")]; } if (BigInt(skylinkData.length) < 99n + fileSize + mdSize + fanoutSize) { return [nu8$4, false, "provided data is too short to contain the full skyfile"]; } let fileData = skylinkData.slice(Number(99n + mdSize + fanoutSize), Number(99n + mdSize + fanoutSize + fileSize)); return [fileData, false, null]; } // @ts-nocheck // json_parse extracted from the json-bigint npm library // regexpxs extracted from // (c) BSD-3-Clause // https://github.com/fastify/secure-json-parse/graphs/contributors and https://github.com/hapijs/bourne/graphs/contributors const suspectProtoRx = /(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])/; const suspectConstructorRx = /(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)/; let json_parse = function (options) { // This is a function that can parse a JSON text, producing a JavaScript // data structure. It is a simple, recursive descent parser. It does not use // eval or regular expressions, so it can be used as a model for implementing // a JSON parser in other languages. // We are defining the function inside of another function to avoid creating // global variables. // Default options one can override by passing options to the parse() let _options = { strict: false, storeAsString: false, alwaysParseAsBig: false, protoAction: "error", constructorAction: "error", }; // If there are options, then use them to override the default _options if (options !== undefined && options !== null) { if (options.strict === true) { _options.strict = true; } if (options.storeAsString === true) { _options.storeAsString = true; } _options.alwaysParseAsBig = options.alwaysParseAsBig === true ? options.alwaysParseAsBig : false; if (typeof options.constructorAction !== "undefined") { if (options.constructorAction === "error" || options.constructorAction === "ignore" || options.constructorAction === "preserve") { _options.constructorAction = options.constructorAction; } else { throw new Error(`Incorrect value for constructorAction option, must be "error", "ignore" or undefined but passed ${options.constructorAction}`); } } if (typeof options.protoAction !== "undefined") { if (options.protoAction === "error" || options.protoAction === "ignore" || options.protoAction === "preserve") { _options.protoAction = options.protoAction; } else { throw new Error(`Incorrect value for protoAction option, must be "error", "ignore" or undefined but passed ${options.protoAction}`); } } } let at, // The index of the current character ch, // The current character escapee = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", }, text, error = function (m) { // Call error when something is wrong. throw { name: "SyntaxError", message: m, at: at, text: text, }; }, next = function (c) { // If a c parameter is provided, verify that it matches the current character. if (c && c !== ch) { error("Expected '" + c + "' instead of '" + ch + "'"); } // Get the next character. When there are no more characters, // return the empty string. ch = text.charAt(at); at += 1; return ch; }, number = function () { // Parse a number value. let number, string = ""; if (ch === "-") { string = "-"; next("-"); } while (ch >= "0" && ch <= "9") { string += ch; next(); } if (ch === ".") { string += "."; while (next() && ch >= "0" && ch <= "9") { string += ch; } } if (ch === "e" || ch === "E") { string += ch; next(); if (ch === "-" || ch === "+") { string += ch; next(); } while (ch >= "0" && ch <= "9") { string += ch; next(); } } number = +string; if (!isFinite(number)) { error("Bad number"); } else { if (Number.isSafeInteger(number)) return !_options.alwaysParseAsBig ? number : BigInt(number); // Number with fractional part should be treated as number(double) including big integers in scientific notation, i.e 1.79e+308 else return _options.storeAsString ? string : /[.eE]/.test(string) ? number : BigInt(string); } }, string = function () { // Parse a string value. let hex, i, string = "", uffff; // When parsing for string values, we must look for " and \ characters. if (ch === '"') { let startAt = at; while (next()) { if (ch === '"') { if (at - 1 > startAt) string += text.substring(startAt, at - 1); next(); return string; } if (ch === "\\") { if (at - 1 > startAt) string += text.substring(startAt, at - 1); next(); if (ch === "u") { uffff = 0; for (i = 0; i < 4; i += 1) { hex = parseInt(next(), 16); if (!isFinite(hex)) { break; } uffff = uffff * 16 + hex; } string += String.fromCharCode(uffff); } else if (typeof escapee[ch] === "string") { string += escapee[ch]; } else { break; } startAt = at; } } } error("Bad string"); }, white = function () { // Skip whitespace. while (ch && ch <= " ") { next(); } }, word = function () { // true, false, or null. switch (ch) { case "t": next("t"); next("r"); next("u"); next("e"); return true; case "f": next("f"); next("a"); next("l"); next("s"); next("e"); return false; case "n": next("n"); next("u"); next("l"); next("l"); return null; } error("Unexpected '" + ch + "'"); }, value, // Place holder for the value function. array = function () { // Parse an array value. let array = []; if (ch === "[") { next("["); white(); if (ch === "]") { next("]"); return array; // empty array } while (ch) { array.push(value()); white(); if (ch === "]") { next("]"); return array; } next(","); white(); } } error("Bad array"); }, object = function () { // Parse an object value. let key, object = Object.create(null); if (ch === "{") { next("{"); white(); if (ch === "}") { next("}"); return object; // empty object } while (ch) { key = string(); white(); next(":"); if (_options.strict === true && Object.hasOwnProperty.call(object, key)) { error('Duplicate key "' + key + '"'); } if (suspectProtoRx.test(key) === true) { if (_options.protoAction === "error") { error("Object contains forbidden prototype property"); } else if (_options.protoAction === "ignore") { value(); } else { object[key] = value(); } } else if (suspectConstructorRx.test(key) === true) { if (_options.constructorAction === "error") { error("Object contains forbidden constructor property"); } else if (_options.constructorAction === "ignore") { value(); } else { object[key] = value(); } } else { object[key] = value(); } white(); if (ch === "}") { next("}"); return object; } next(","); white(); } } error("Bad object"); }; value = function () { // Parse a JSON value. It could be an object, an array, a string, a number, // or a word. white(); switch (ch) { case "{": return object(); case "[": return array(); case '"': return string(); case "-": return number(); default: return ch >= "0" && ch <= "9" ? number() : word(); } }; // Return the json_parse function. It will have access to all of the above // functions and variables. return function (source, reviver) { let result; text = source + ""; at = 0; ch = " "; result = value(); white(); if (ch) { error("Syntax error"); } // If there is a reviver function, we recursively walk the new structure, // passing each name/value pair to the reviver function for possible // transformation, starting with a temporary root object that holds the result // in an empty key. If there is not a reviver function, we simply return the // result. return typeof reviver === "function" ? (function walk(holder, key) { let v, value = holder[key]; if (value && typeof value === "object") { Object.keys(value).forEach(function (k) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } }); } return reviver.call(holder, key, value); })({ "": result }, "") : result; }; }; // parseJSON is a wrapper for JSONbig.parse that returns an error rather than // throwing an error. JSONbig is an alternative to JSON.parse that decodes // every number as a bigint. This is required when working with the skyd API // because the skyd API uses 64 bit precision for all of its numbers, and // therefore cannot be parsed losslessly by javascript. The skyd API is // cryptographic, therefore full precision is required. function parseJSON(json) { try { let obj = json_parse({ alwaysParseAsBig: true })(json); return [obj, null]; } catch (err) { return [{}, tryStringify(err)]; } } // Helper consts that make it easier to return empty values when returning an // error inside of a function. const nu8$3 = new Uint8Array(0); // parseSkylinkBitfield parses a skylink bitfield and returns the corresponding // version, offset, and fetchSize. function parseSkylinkBitfield(skylink) { // Validate the input. if (skylink.length !== 34) { return [0n, 0n, 0n, "provided skylink has incorrect length"]; } // Extract the bitfield. let bitfield = new DataView(skylink.buffer).getUint16(0, true); // Extract the version. let version = (bitfield & 3) + 1; // Only versions 1 and 2 are recognized. if (version !== 1 && version !== 2) { return [0n, 0n, 0n, "provided skylink has unrecognized version"]; } // If the skylink is set to version 2, we only recognize the link if // the rest of the bits in the bitfield are empty. if (version === 2) { if ((bitfield & 3) !== bitfield) { return [0n, 0n, 0n, "provided skylink has unrecognized version"]; } return [BigInt(version), 0n, 0n, null]; } // Verify that the mode is valid, then fetch the mode. bitfield = bitfield >> 2; if ((bitfield & 255) === 255) { return [0n, 0n, 0n, "provided skylink has an unrecognized version"]; } let mode = 0; for (let i = 0; i < 8; i++) { if ((bitfield & 1) === 0) { bitfield = bitfield >> 1; break; } bitfield = bitfield >> 1; mode++; } // If the mode is greater than 7, this is not a valid v1 skylink. if (mode > 7) { return [0n, 0n, 0n, "provided skylink has an invalid v1 bitfield"]; } // Determine the offset and fetchSize increment. let offsetIncrement = 4096 << mode; let fetchSizeIncrement = 4096; let fetchSizeStart = 0; if (mode > 0) { fetchSizeIncrement = fetchSizeIncrement << (mode - 1); fetchSizeStart = (1 << 15) << (mode - 1); } // The next three bits decide the fetchSize. let fetchSizeBits = bitfield & 7; fetchSizeBits++; // semantic upstep, range should be [1,8] not [0,8). let fetchSize = fetchSizeBits * fetchSizeIncrement + fetchSizeStart; bitfield = bitfield >> 3; // The remaining bits determine the offset. let offset = bitfield * offsetIncrement; if (offset + fetchSize > 1 << 22) { return [0n, 0n, 0n, "provided skylink has an invalid v1 bitfield"]; } // Return what we learned. return [BigInt(version), BigInt(offset), BigInt(fetchSize), null]; } // skylinkV1Bitfield sets the bitfield of a V1 skylink. It assumes the version // is 1 and the offset is 0. It will determine the appropriate fetchSize from // the provided dataSize. function skylinkV1Bitfield(dataSizeBI) { // Check that the dataSize is not too large. if (dataSizeBI > 1 << 22) { return [nu8$3, "dataSize must be less than the sector size"]; } let dataSize = Number(dataSizeBI); // Determine the mode for the file. The mode is determined by the // dataSize. let mode = 0; for (let i = 1 << 15; i < dataSize; i *= 2) { mode += 1; } // Determine the download number. let downloadNumber = 0; if (mode === 0) { if (dataSize !== 0) { downloadNumber = Math.floor((dataSize - 1) / (1 << 12)); } } else { let step = 1 << (11 + mode); let target = dataSize - (1 << (14 + mode)); if (target !== 0) { downloadNumber = Math.floor((target - 1) / step); } } // Create the Uint8Array and fill it out. The main reason I switch over // the 7 modes like this is because I wasn't sure how to make a uint16 // in javascript. If we could treat the uint8array as a uint16 and then // later convert it over, we could use basic bitshifiting and really // simplify the code here. let bitfield = new Uint8Array(2); if (mode === 7) { // 0 0 0 X X X 0 1|1 1 1 1 1 1 0 0 bitfield[0] = downloadNumber; bitfield[0] *= 4; bitfield[0] += 1; bitfield[1] = 4 + 8 + 16 + 32 + 64 + 128; } if (mode === 6) { // 0 0 0 0 X X X 0|1 1 1 1 1 1 0 0 bitfield[0] = downloadNumber; bitfield[0] *= 2; bitfield[1] = 4 + 8 + 16 + 32 + 64 + 128; } if (mode === 5) { // 0 0 0 0 0 X X X|0 1 1 1 1 1 0 0 bitfield[0] = downloadNumber; bitfield[1] = 4 + 8 + 16 + 32 + 64; } if (mode === 4) { // 0 0 0 0 0 0 X X|X 0 1 1 1 1 0 0 bitfield[0] = downloadNumber; bitfield[0] /= 2; bitfield[1] = (downloadNumber & 1) * 128; bitfield[1] += 4 + 8 + 16 + 32; } if (mode === 3) { // 0 0 0 0 0 0 0 X|X X 0 1 1 1 0 0 bitfield[0] = downloadNumber; bitfield[0] /= 4; bitfield[1] = (downloadNumber & 3) * 64; bitfield[1] += 4 + 8 + 16; } if (mode === 2) { // 0 0 0 0 0 0 0 0|X X X 0 1 1 0 0 bitfield[0] = 0; bitfield[1] = downloadNumber * 32; bitfield[1] += 4 + 8; } if (mode === 1) { // 0 0 0 0 0 0 0 0|0 X X X 0 1 0 0 bitfield[0] = 0; bitfield[1] = downloadNumber * 16; bitfield[1] += 4; } if (mode === 0) { // 0 0 0 0 0 0 0 0|0 0 X X X 0 0 0 bitfield[0] = 0; bitfield[1] = downloadNumber * 8; } // Swap the byte order. let zero = bitfield[0]; bitfield[0] = bitfield[1]; bitfield[1] = zero; return [bitfield, null]; } const HASH_SIZE = 64; const K = [ 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817, ]; function ts64(x, i, h, l) { x[i] = (h >> 24) & 0xff; x[i + 1] = (h >> 16) & 0xff; x[i + 2] = (h >> 8) & 0xff; x[i + 3] = h & 0xff; x[i + 4] = (l >> 24) & 0xff; x[i + 5] = (l >> 16) & 0xff; x[i + 6] = (l >> 8) & 0xff; x[i + 7] = l & 0xff; } function crypto_hashblocks_hl(hh, hl, m, n) { let wh = new Int32Array(16), wl = new Int32Array(16), bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7, bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7, th, tl, i, j, h, l, a, b, c, d; let ah0 = hh[0], ah1 = hh[1], ah2 = hh[2], ah3 = hh[3], ah4 = hh[4], ah5 = hh[5], ah6 = hh[6], ah7 = hh[7], al0 = hl[0], al1 = hl[1], al2 = hl[2], al3 = hl[3], al4 = hl[4], al5 = hl[5], al6 = hl[6], al7 = hl[7]; let pos = 0; while (n >= 128) { for (i = 0; i < 16; i++) { j = 8 * i + pos; wh[i] = (m[j + 0] << 24) | (m[j + 1] << 16) | (m[j + 2] << 8) | m[j + 3]; wl[i] = (m[j + 4] << 24) | (m[j + 5] << 16) | (m[j + 6] << 8) | m[j + 7]; } for (i = 0; i < 80; i++) { bh0 = ah0; bh1 = ah1; bh2 = ah2; bh3 = ah3; bh4 = ah4; bh5 = ah5; bh6 = ah6; bh7 = ah7; bl0 = al0; bl1 = al1; bl2 = al2; bl3 = al3; bl4 = al4; bl5 = al5; bl6 = al6; bl7 = al7; // add h = ah7; l = al7; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; // Sigma1 h = ((ah4 >>> 14) | (al4 << (32 - 14))) ^ ((ah4 >>> 18) | (al4 << (32 - 18))) ^ ((al4 >>> (41 - 32)) | (ah4 << (32 - (41 - 32)))); l = ((al4 >>> 14) | (ah4 << (32 - 14))) ^ ((al4 >>> 18) | (ah4 << (32 - 18))) ^ ((ah4 >>> (41 - 32)) | (al4 << (32 - (41 - 32)))); a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; // Ch h = (ah4 & ah5) ^ (~ah4 & ah6); l = (al4 & al5) ^ (~al4 & al6); a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; // K h = K[i * 2]; l = K[i * 2 + 1]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; // w h = wh[i % 16]; l = wl[i % 16]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; th = (c & 0xffff) | (d << 16); tl = (a & 0xffff) | (b << 16); // add h = th; l = tl; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; // Sigma0 h = ((ah0 >>> 28) | (al0 << (32 - 28))) ^ ((al0 >>> (34 - 32)) | (ah0 << (32 - (34 - 32)))) ^ ((al0 >>> (39 - 32)) | (ah0 << (32 - (39 - 32)))); l = ((al0 >>> 28) | (ah0 << (32 - 28))) ^ ((ah0 >>> (34 - 32)) | (al0 << (32 - (34 - 32)))) ^ ((ah0 >>> (39 - 32)) | (al0 << (32 - (39 - 32)))); a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; // Maj h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2); l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2); a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; bh7 = (c & 0xffff) | (d << 16); bl7 = (a & 0xffff) | (b << 16); // add h = bh3; l = bl3; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = th; l = tl; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; bh3 = (c & 0xffff) | (d << 16); bl3 = (a & 0xffff) | (b << 16); ah1 = bh0; ah2 = bh1; ah3 = bh2; ah4 = bh3; ah5 = bh4; ah6 = bh5; ah7 = bh6; ah0 = bh7; al1 = bl0; al2 = bl1; al3 = bl2; al4 = bl3; al5 = bl4; al6 = bl5; al7 = bl6; al0 = bl7; if (i % 16 === 15) { for (j = 0; j < 16; j++) { // add h = wh[j]; l = wl[j]; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = wh[(j + 9) % 16]; l = wl[(j + 9) % 16]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; // sigma0 th = wh[(j + 1) % 16]; tl = wl[(j + 1) % 16]; h = ((th >>> 1) | (tl << (32 - 1))) ^ ((th >>> 8) | (tl << (32 - 8))) ^ (th >>> 7); l = ((tl >>> 1) | (th << (32 - 1))) ^ ((tl >>> 8) | (th << (32 - 8))) ^ ((tl >>> 7) | (th << (32 - 7))); a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; // sigma1 th = wh[(j + 14) % 16]; tl = wl[(j + 14) % 16]; h = ((th >>> 19) | (tl << (32 - 19))) ^ ((tl >>> (61 - 32)) | (th << (32 - (61 - 32)))) ^ (th >>> 6); l = ((tl >>> 19) | (th << (32 - 19))) ^ ((th >>> (61 - 32)) | (tl << (32 - (61 - 32)))) ^ ((tl >>> 6) | (th << (32 - 6))); a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; wh[j] = (c & 0xffff) | (d << 16); wl[j] = (a & 0xffff) | (b << 16); } } } // add h = ah0; l = al0; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[0]; l = hl[0]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[0] = ah0 = (c & 0xffff) | (d << 16); hl[0] = al0 = (a & 0xffff) | (b << 16); h = ah1; l = al1; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[1]; l = hl[1]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[1] = ah1 = (c & 0xffff) | (d << 16); hl[1] = al1 = (a & 0xffff) | (b << 16); h = ah2; l = al2; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[2]; l = hl[2]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[2] = ah2 = (c & 0xffff) | (d << 16); hl[2] = al2 = (a & 0xffff) | (b << 16); h = ah3; l = al3; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[3]; l = hl[3]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[3] = ah3 = (c & 0xffff) | (d << 16); hl[3] = al3 = (a & 0xffff) | (b << 16); h = ah4; l = al4; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[4]; l = hl[4]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[4] = ah4 = (c & 0xffff) | (d << 16); hl[4] = al4 = (a & 0xffff) | (b << 16); h = ah5; l = al5; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[5]; l = hl[5]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[5] = ah5 = (c & 0xffff) | (d << 16); hl[5] = al5 = (a & 0xffff) | (b << 16); h = ah6; l = al6; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[6]; l = hl[6]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[6] = ah6 = (c & 0xffff) | (d << 16); hl[6] = al6 = (a & 0xffff) | (b << 16); h = ah7; l = al7; a = l & 0xffff; b = l >>> 16; c = h & 0xffff; d = h >>> 16; h = hh[7]; l = hl[7]; a += l & 0xffff; b += l >>> 16; c += h & 0xffff; d += h >>> 16; b += a >>> 16; c += b >>> 16; d += c >>> 16; hh[7] = ah7 = (c & 0xffff) | (d << 16); hl[7] = al7 = (a & 0xffff) | (b << 16); pos += 128; n -= 128; } return n; } const sha512internal = function (out, m, n) { let hh = new Int32Array(8), hl = new Int32Array(8), x = new Uint8Array(256), i, b = n; hh[0] = 0x6a09e667; hh[1] = 0xbb67ae85; hh[2] = 0x3c6ef372; hh[3] = 0xa54ff53a; hh[4] = 0x510e527f; hh[5] = 0x9b05688c; hh[6] = 0x1f83d9ab; hh[7] = 0x5be0cd19; hl[0] = 0xf3bcc908; hl[1] = 0x84caa73b; hl[2] = 0xfe94f82b; hl[3] = 0x5f1d36f1; hl[4] = 0xade682d1; hl[5] = 0x2b3e6c1f; hl[6] = 0xfb41bd6b; hl[7] = 0x137e2179; crypto_hashblocks_hl(hh, hl, m, n); n %= 128; for (i = 0; i < n; i++) x[i] = m[b - n + i]; x[n] = 128; n = 256 - 128 * (n < 112 ? 1 : 0); x[n - 9] = 0; ts64(x, n - 8, (b / 0x20000000) | 0, b << 3); crypto_hashblocks_hl(hh, hl, x, n); for (i = 0; i < 8; i++) ts64(out, 8 * i, hh[i], hl[i]); return 0; }; // sha512 is the standard sha512 cryptographic hash function. This is the // default choice for Skynet operations, though many of the Sia protocol // standards use blake2b instead, so you will see both. function sha512(m) { const out = new Uint8Array(HASH_SIZE); sha512internal(out, m, m.length); return out; } let crypto_sign_BYTES = 64, crypto_sign_PUBLICKEYBYTES = 32, crypto_sign_SECRETKEYBYTES = 64, crypto_sign_SEEDBYTES = 32; let gf = function () { let r = new Float64Array(16); return r; }; let gfi = function (init) { let i, r = new Float64Array(16); if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; return r; }; let gf0 = gf(), gf1 = gfi([1]), D = gfi([ 0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203, ]), D2 = gfi([ 0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406, ]), X = gfi([ 0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169, ]), Y = gfi([ 0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, ]), I = gfi([ 0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83, ]); function vn(x, xi, y, yi, n) { let i, d = 0; for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; return (1 & ((d - 1) >>> 8)) - 1; } function crypto_verify_32(x, xi, y, yi) { return vn(x, xi, y, yi, 32); } function set25519(r, a) { let i; for (i = 0; i < 16; i++) r[i] = a[i] | 0; } function car25519(o) { let i, v, c = 1; for (i = 0; i < 16; i++) { v = o[i] + c + 65535; c = Math.floor(v / 65536); o[i] = v - c * 65536; } o[0] += c - 1 + 37 * (c - 1); } function sel25519(p, q, b) { let t, c = ~(b - 1); for (let i = 0; i < 16; i++) { t = c & (p[i] ^ q[i]); p[i] ^= t; q[i] ^= t; } } function pack25519(o, n) { let i, j, b; let m = gf(), t = gf(); for (i = 0; i < 16; i++) t[i] = n[i]; car25519(t); car25519(t); car25519(t); for (j = 0; j < 2; j++) { m[0] = t[0] - 0xffed; for (i = 1; i < 15; i++) { m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); m[i - 1] &= 0xffff; } m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); b = (m[15] >> 16) & 1; m[14] &= 0xffff; sel25519(t, m, 1 - b); } for (i = 0; i < 16; i++) { o[2 * i] = t[i] & 0xff; o[2 * i + 1] = t[i] >> 8; } } function neq25519(a, b) { let c = new Uint8Array(32), d = new Uint8Array(32); pack25519(c, a); pack25519(d, b); return crypto_verify_32(c, 0, d, 0); } function par25519(a) { let d = new Uint8Array(32); pack25519(d, a); return d[0] & 1; } function unpack25519(o, n) { let i; for (i = 0; i < 16; i++) o[i] = n[2 * i] + (n[2 * i + 1] << 8); o[15] &= 0x7fff; } function A(o, a, b) { for (let i = 0; i < 16; i++) o[i] = a[i] + b[i]; } function Z(o, a, b) { for (let i = 0; i < 16; i++) o[i] = a[i] - b[i]; } function M(o, a, b) { let v, c, t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0, b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5], b6 = b[6], b7 = b[7], b8 = b[8], b9 = b[9], b10 = b[10], b11 = b[11], b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15]; v = a[0]; t0 += v * b0; t1 += v * b1; t2 += v * b2; t3 += v * b3; t4 += v * b4; t5 += v * b5; t6 += v * b6; t7 += v * b7; t8 += v * b8; t9 += v * b9; t10 += v * b10; t11 += v * b11; t12 += v * b12; t13 += v * b13; t14 += v * b14; t15 += v * b15; v = a[1]; t1 += v * b0; t2 += v * b1; t3 += v * b2; t4 += v * b3; t5 += v * b4; t6 += v * b5; t7 += v * b6; t8 += v * b7; t9 += v * b8; t10 += v * b9; t11 += v * b10; t12 += v * b11; t13 += v * b12; t14 += v * b13; t15 += v * b14; t16 += v * b15; v = a[2]; t2 += v * b0; t3 += v * b1; t4 += v * b2; t5 += v * b3; t6 += v * b4; t7 += v * b5; t8 += v * b6; t9 += v * b7; t10 += v * b8; t11 += v * b9; t12 += v * b10; t13 += v * b11; t14 += v * b12; t15 += v * b13; t16 += v * b14; t17 += v * b15; v = a[3]; t3 += v * b0; t4 += v * b1; t5 += v * b2; t6 += v * b3; t7 += v * b4; t8 += v * b5; t9 += v * b6; t10 += v * b7; t11 += v * b8; t12 += v * b9; t13 += v * b10; t14 += v * b11; t15 += v * b12; t16 += v * b13; t17 += v * b14; t18 += v * b15; v = a[4]; t4 += v * b0; t5 += v * b1; t6 += v * b2; t7 += v * b3; t8 += v * b4; t9 += v * b5; t10 += v * b6; t11 += v * b7; t12 += v * b8; t13 += v * b9; t14 += v * b10; t15 += v * b11; t16 += v * b12; t17 += v * b13; t18 += v * b14; t19 += v * b15; v = a[5]; t5 += v * b0; t6 += v * b1; t7 += v * b2; t8 += v * b3; t9 += v * b4; t10 += v * b5; t11 += v * b6; t12 += v * b7; t13 += v * b8; t14 += v * b9; t15 += v * b10; t16 += v * b11; t17 += v * b12; t18 += v * b13; t19 += v * b14; t20 += v * b15; v = a[6]; t6 += v * b0; t7 += v * b1; t8 += v * b2; t9 += v * b3; t10 += v * b4; t11 += v * b5; t12 += v * b6; t13 += v * b7; t14 += v * b8; t15 += v * b9; t16 += v * b10; t17 += v * b11; t18 += v * b12; t19 += v * b13; t20 += v * b14; t21 += v * b15; v = a[7]; t7 += v * b0; t8 += v * b1; t9 += v * b2; t10 += v * b3; t11 += v * b4; t12 += v * b5; t13 += v * b6; t14 += v * b7; t15 += v * b8; t16 += v * b9; t17 += v * b10; t18 += v * b11; t19 += v * b12; t20 += v * b13; t21 += v * b14; t22 += v * b15; v = a[8]; t8 += v * b0; t9 += v * b1; t10 += v * b2; t11 += v * b3; t12 += v * b4; t13 += v * b5; t14 += v * b6; t15 += v * b7; t16 += v * b8; t17 += v * b9; t18 += v * b10; t19 += v * b11; t20 += v * b12; t21 += v * b13; t22 += v * b14; t23 += v * b15; v = a[9]; t9 += v * b0; t10 += v * b1; t11 += v * b2; t12 += v * b3; t13 += v * b4; t14 += v * b5; t15 += v * b6; t16 += v * b7; t17 += v * b8; t18 += v * b9; t19 += v * b10; t20 += v * b11; t21 += v * b12; t22 += v * b13; t23 += v * b14; t24 += v * b15; v = a[10]; t10 += v * b0; t11 += v * b1; t12 += v * b2; t13 += v * b3; t14 += v * b4; t15 += v * b5; t16 += v * b6; t17 += v * b7; t18 += v * b8; t19 += v * b9; t20 += v * b10; t21 += v * b11; t22 += v * b12; t23 += v * b13; t24 += v * b14; t25 += v * b15; v = a[11]; t11 += v * b0; t12 += v * b1; t13 += v * b2; t14 += v * b3; t15 += v * b4; t16 += v * b5; t17 += v * b6; t18 += v * b7; t19 += v * b8; t20 += v * b9; t21 += v * b10; t22 += v * b11; t23 += v * b12; t24 += v * b13; t25 += v * b14; t26 += v * b15; v = a[12]; t12 += v * b0; t13 += v * b1; t14 += v * b2; t15 += v * b3; t16 += v * b4; t17 += v * b5; t18 += v * b6; t19 += v * b7; t20 += v * b8; t21 += v * b9; t22 += v * b10; t23 += v * b11; t24 += v * b12; t25 += v * b13; t26 += v * b14; t27 += v * b15; v = a[13]; t13 += v * b0; t14 += v * b1; t15 += v * b2; t16 += v * b3; t17 += v * b4; t18 += v * b5; t19 += v * b6; t20 += v * b7; t21 += v * b8; t22 += v * b9; t23 += v * b10; t24 += v * b11; t25 += v * b12; t26 += v * b13; t27 += v * b14; t28 += v * b15; v = a[14]; t14 += v * b0; t15 += v * b1; t16 += v * b2; t17 += v * b3; t18 += v * b4; t19 += v * b5; t20 += v * b6; t21 += v * b7; t22 += v * b8; t23 += v * b9; t24 += v * b10; t25 += v * b11; t26 += v * b12; t27 += v * b13; t28 += v * b14; t29 += v * b15; v = a[15]; t15 += v * b0; t16 += v * b1; t17 += v * b2; t18 += v * b3; t19 += v * b4; t20 += v * b5; t21 += v * b6; t22 += v * b7; t23 += v * b8; t24 += v * b9; t25 += v * b10; t26 += v * b11; t27 += v * b12; t28 += v * b13; t29 += v * b14; t30 += v * b15; t0 += 38 * t16; t1 += 38 * t17; t2 += 38 * t18; t3 += 38 * t19; t4 += 38 * t20; t5 += 38 * t21; t6 += 38 * t22; t7 += 38 * t23; t8 += 38 * t24; t9 += 38 * t25; t10 += 38 * t26; t11 += 38 * t27; t12 += 38 * t28; t13 += 38 * t29; t14 += 38 * t30; // t15 left as is // first car c = 1; v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; t0 += c - 1 + 37 * (c - 1); // second car c = 1; v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; t0 += c - 1 + 37 * (c - 1); o[0] = t0; o[1] = t1; o[2] = t2; o[3] = t3; o[4] = t4; o[5] = t5; o[6] = t6; o[7] = t7; o[8] = t8; o[9] = t9; o[10] = t10; o[11] = t11; o[12] = t12; o[13] = t13; o[14] = t14; o[15] = t15; } function S(o, a) { M(o, a, a); } function inv25519(o, i) { let c = gf(); let a; for (a = 0; a < 16; a++) c[a] = i[a]; for (a = 253; a >= 0; a--) { S(c, c); if (a !== 2 && a !== 4) M(c, c, i); } for (a = 0; a < 16; a++) o[a] = c[a]; } function pow2523(o, i) { let c = gf(); let a; for (a = 0; a < 16; a++) c[a] = i[a]; for (a = 250; a >= 0; a--) { S(c, c); if (a !== 1) M(c, c, i); } for (a = 0; a < 16; a++) o[a] = c[a]; } function add(p, q) { let a = gf(), b = gf(), c = gf(), d = gf(), e = gf(), f = gf(), g = gf(), h = gf(), t = gf(); Z(a, p[1], p[0]); Z(t, q[1], q[0]); M(a, a, t); A(b, p[0], p[1]); A(t, q[0], q[1]); M(b, b, t); M(c, p[3], q[3]); M(c, c, D2); M(d, p[2], q[2]); A(d, d, d); Z(e, b, a); Z(f, d, c); A(g, d, c); A(h, b, a); M(p[0], e, f); M(p[1], h, g); M(p[2], g, f); M(p[3], e, h); } function cswap(p, q, b) { let i; for (i = 0; i < 4; i++) { sel25519(p[i], q[i], b); } } function pack(r, p) { let tx = gf(), ty = gf(), zi = gf(); inv25519(zi, p[2]); M(tx, p[0], zi); M(ty, p[1], zi); pack25519(r, ty); r[31] ^= par25519(tx) << 7; } function scalarmult(p, q, s) { let b, i; set25519(p[0], gf0); set25519(p[1], gf1); set25519(p[2], gf1); set25519(p[3], gf0); for (i = 255; i >= 0; --i) { b = (s[(i / 8) | 0] >> (i & 7)) & 1; cswap(p, q, b); add(q, p); add(p, p); cswap(p, q, b); } } function scalarbase(p, s) { let q = [gf(), gf(), gf(), gf()]; set25519(q[0], X); set25519(q[1], Y); set25519(q[2], gf1); M(q[3], X, Y); scalarmult(p, q, s); } let L = new Float64Array([ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, ]); function modL(r, x) { let carry, i, j, k; for (i = 63; i >= 32; --i) { carry = 0; for (j = i - 32, k = i - 12; j < k; ++j) { x[j] += carry - 16 * x[i] * L[j - (i - 32)]; carry = Math.floor((x[j] + 128) / 256); x[j] -= carry * 256; } x[j] += carry; x[i] = 0; } carry = 0; for (j = 0; j < 32; j++) { x[j] += carry - (x[31] >> 4) * L[j]; carry = x[j] >> 8; x[j] &= 255; } for (j = 0; j < 32; j++) x[j] -= carry * L[j]; for (i = 0; i < 32; i++) { x[i + 1] += x[i] >> 8; r[i] = x[i] & 255; } } function unpackneg(r, p) { let t = gf(), chk = gf(), num = gf(), den = gf(), den2 = gf(), den4 = gf(), den6 = gf(); set25519(r[2], gf1); unpack25519(r[1], p); S(num, r[1]); M(den, num, D); Z(num, num, r[2]); A(den, r[2], den); S(den2, den); S(den4, den2); M(den6, den4, den2); M(t, den6, num); M(t, t, den); pow2523(t, t); M(t, t, num); M(t, t, den); M(t, t, den); M(r[0], t, den); S(chk, r[0]); M(chk, chk, den); if (neq25519(chk, num)) M(r[0], r[0], I); S(chk, r[0]); M(chk, chk, den); if (neq25519(chk, num)) return -1; if (par25519(r[0]) === p[31] >> 7) Z(r[0], gf0, r[0]); M(r[3], r[0], r[1]); return 0; } function reduce(r) { let x = new Float64Array(64), i; for (i = 0; i < 64; i++) x[i] = r[i]; for (i = 0; i < 64; i++) r[i] = 0; modL(r, x); } function crypto_sign_keypair(pk, sk) { let d = new Uint8Array(64); let p = [gf(), gf(), gf(), gf()]; let i; sha512internal(d, sk, 32); d[0] &= 248; d[31] &= 127; d[31] |= 64; scalarbase(p, d); pack(pk, p); for (i = 0; i < 32; i++) sk[i + 32] = pk[i]; return 0; } function crypto_sign_open(m, sm, n, pk) { let i; let t = new Uint8Array(32), h = new Uint8Array(64); let p = [gf(), gf(), gf(), gf()], q = [gf(), gf(), gf(), gf()]; if (n < 64) return -1; if (unpackneg(q, pk)) return -1; for (i = 0; i < n; i++) m[i] = sm[i]; for (i = 0; i < 32; i++) m[i + 32] = pk[i]; sha512internal(h, m, n); reduce(h); scalarmult(p, q, h); scalarbase(q, sm.subarray(32)); add(p, q); pack(t, p); n -= 64; if (crypto_verify_32(sm, 0, t, 0)) { for (i = 0; i < n; i++) m[i] = 0; return -1; } for (i = 0; i < n; i++) m[i] = sm[i + 64]; return n; } // Note: difference from C - smlen returned, not passed as argument. function crypto_sign(sm, m, n, sk) { let d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64); let i, j, x = new Float64Array(64); let p = [gf(), gf(), gf(), gf()]; sha512internal(d, sk, 32); d[0] &= 248; d[31] &= 127; d[31] |= 64; let smlen = n + 64; for (i = 0; i < n; i++) sm[64 + i] = m[i]; for (i = 0; i < 32; i++) sm[32 + i] = d[32 + i]; sha512internal(r, sm.subarray(32), n + 32); reduce(r); scalarbase(p, r); pack(sm, p); for (i = 32; i < 64; i++) sm[i] = sk[i]; sha512internal(h, sm, n + 64); reduce(h); for (i = 0; i < 64; i++) x[i] = 0; for (i = 0; i < 32; i++) x[i] = r[i]; for (i = 0; i < 32; i++) { for (j = 0; j < 32; j++) { x[i + j] += h[i] * d[j]; } } modL(sm.subarray(32), x); return smlen; } // Zero types to make error returns more convenient. const nu8$2 = new Uint8Array(0); const nkp$1 = { publicKey: nu8$2, secretKey: nu8$2 }; // checkAllUint8Array is a helper function to perform input checking on the // crypto API functions. Because the kernel is often hot-loading untrusted // code, we cannot depend on typescript to provide type safety. function checkAllUint8Array(...args) { for (let i = 0; i < args.length; i++) { if (!(args[i] instanceof Uint8Array)) { return "unexpected type, use Uint8Array"; } } return null; } // ed25519KeypairFromEntropy is a function that generates an ed25519 keypair // from the provided entropy. function ed25519KeypairFromEntropy(seed) { // Input checking. let errU8 = checkAllUint8Array(seed); if (errU8 !== null) { return [nkp$1, addContextToErr(errU8, "seed is invalid")]; } if (seed.length !== crypto_sign_SEEDBYTES) { return [nkp$1, "bad seed size"]; } // Build the keypair. let pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); let sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); for (let i = 0; i < 32; i++) { sk[i] = seed[i]; } crypto_sign_keypair(pk, sk); return [ { publicKey: pk, secretKey: sk, }, null, ]; } // ed25519Sign will produce an ed25519 signature of a given input. function ed25519Sign(msg, secretKey) { // Input checking. let errU8 = checkAllUint8Array(msg, secretKey); if (errU8 !== null) { return [nu8$2, addContextToErr(errU8, "inputs are invalid")]; } if (secretKey.length !== crypto_sign_SECRETKEYBYTES) { return [nu8$2, "bad secret key size"]; } // Build the signature. let signedMsg = new Uint8Array(crypto_sign_BYTES + msg.length); crypto_sign(signedMsg, msg, msg.length, secretKey); let sig = new Uint8Array(crypto_sign_BYTES); for (let i = 0; i < sig.length; i++) { sig[i] = signedMsg[i]; } return [sig, null]; } // ed25519Verify will check whether a signature is valid against the given // publicKey and message. function ed25519Verify(msg, sig, publicKey) { let errU8 = checkAllUint8Array(msg, sig, publicKey); if (errU8 !== null) { return false; } if (sig.length !== crypto_sign_BYTES) { return false; } if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) { return false; } let sm = new Uint8Array(crypto_sign_BYTES + msg.length); let m = new Uint8Array(crypto_sign_BYTES + msg.length); let i; for (i = 0; i < crypto_sign_BYTES; i++) { sm[i] = sig[i]; } for (i = 0; i < msg.length; i++) { sm[i + crypto_sign_BYTES] = msg[i]; } return crypto_sign_open(m, sm, sm.length, publicKey) >= 0; } // Define the number of entropy words used when generating the seed. const SEED_ENTROPY_WORDS = 13; const SEED_BYTES = 16; // deriveChildSeed is a helper function to derive a child seed from a parent // seed using a string as the path. function deriveChildSeed(parentSeed, derivationTag) { let tagU8 = new TextEncoder().encode(" - " + derivationTag); let preimage = new Uint8Array(parentSeed.length + tagU8.length); preimage.set(parentSeed, 0); preimage.set(tagU8, parentSeed.length); let hash = sha512(preimage); return hash.slice(0, SEED_BYTES); } // deriveMyskyRoot is a helper function to derive the root mysky seed of the // provided user seed. // // NOTE: This is code is to provide legacy compatibility with the MySky // ecosystem. Compatibility cannot be broken here. function deriveMyskyRootKeypair(userSeed) { let saltBytes = new TextEncoder().encode("root discoverable key"); let saltHash = sha512(saltBytes); let userSeedHash = sha512(userSeed); let mergedHash = sha512(new Uint8Array([...saltHash, ...userSeedHash])); let keyEntropy = mergedHash.slice(0, 32); // Error is ignored because it should not be possible with the provided // inputs. let [keypair] = ed25519KeypairFromEntropy(keyEntropy); return keypair; } // generateSeedPhraseDeterministic will generate and verify a seed phrase for // the user. function generateSeedPhraseDeterministic(password) { let u8 = new TextEncoder().encode(password); let buf = sha512(u8); let randNums = Uint16Array.from(buf); // Generate the seed phrase from the randNums. let seedWords = []; for (let i = 0; i < SEED_ENTROPY_WORDS; i++) { let wordIndex = randNums[i] % dictionary.length; if (i == SEED_ENTROPY_WORDS - 1) { wordIndex = randNums[i] % (dictionary.length / 4); } seedWords.push(dictionary[wordIndex]); } // Convert the seedWords to a seed. let [seed, err1] = seedWordsToSeed(seedWords); if (err1 !== null) { return ["", err1]; } // Compute the checksum. let [checksumOne, checksumTwo, err2] = seedToChecksumWords(seed); if (err2 !== null) { return ["", err2]; } // Assemble the final seed phrase and set the text field. let allWords = [...seedWords, checksumOne, checksumTwo]; let seedPhrase = allWords.join(" "); return [seedPhrase, null]; } // seedToChecksumWords will compute the two checksum words for the provided // seed. The two return values are the two checksum words. function seedToChecksumWords(seed) { // Input validation. if (seed.length !== SEED_BYTES) { return ["", "", `seed has the wrong length: ${seed.length}`]; } // Get the hash. let h = sha512(seed); // Turn the hash into two words. let word1 = h[0] << 8; word1 += h[1]; word1 >>= 6; let word2 = h[1] << 10; word2 &= 0xffff; word2 += h[2] << 2; word2 >>= 6; return [dictionary[word1], dictionary[word2], null]; } // validSeedPhrase checks whether the provided seed phrase is valid, returning // an error if not. If the seed phrase is valid, the full seed will be returned // as a Uint8Array. function validSeedPhrase(seedPhrase) { // Create a helper function to make the below code more readable. let prefix = function (s) { return s.slice(0, DICTIONARY_UNIQUE_PREFIX); }; // Pull the seed into its respective parts. let seedWordsAndChecksum = seedPhrase.split(" "); let seedWords = seedWordsAndChecksum.slice(0, SEED_ENTROPY_WORDS); let checksumOne = seedWordsAndChecksum[SEED_ENTROPY_WORDS]; let checksumTwo = seedWordsAndChecksum[SEED_ENTROPY_WORDS + 1]; // Convert the seedWords to a seed. let [seed, err1] = seedWordsToSeed(seedWords); if (err1 !== null) { return [new Uint8Array(0), addContextToErr(err1, "unable to parse seed phrase")]; } let [checksumOneVerify, checksumTwoVerify, err2] = seedToChecksumWords(seed); if (err2 !== null) { return [new Uint8Array(0), addContextToErr(err2, "could not compute checksum words")]; } if (prefix(checksumOne) !== prefix(checksumOneVerify)) { return [new Uint8Array(0), "first checksum word is invalid"]; } if (prefix(checksumTwo) !== prefix(checksumTwoVerify)) { return [new Uint8Array(0), "second checksum word is invalid"]; } return [seed, null]; } // seedWordsToSeed will convert a provided seed phrase to to a Uint8Array that // represents the cryptographic seed in bytes. function seedWordsToSeed(seedWords) { // Input checking. if (seedWords.length !== SEED_ENTROPY_WORDS) { return [new Uint8Array(0), `Seed words should have length ${SEED_ENTROPY_WORDS} but has length ${seedWords.length}`]; } // We are getting 16 bytes of entropy. let bytes = new Uint8Array(SEED_BYTES); let curByte = 0; let curBit = 0; for (let i = 0; i < SEED_ENTROPY_WORDS; i++) { // Determine which number corresponds to the next word. let word = -1; for (let j = 0; j < dictionary.length; j++) { if (seedWords[i].slice(0, DICTIONARY_UNIQUE_PREFIX) === dictionary[j].slice(0, DICTIONARY_UNIQUE_PREFIX)) { word = j; break; } } if (word === -1) { return [new Uint8Array(0), `word '${seedWords[i]}' at index ${i} not found in dictionary`]; } let wordBits = 10; if (i === SEED_ENTROPY_WORDS - 1) { wordBits = 8; } // Iterate over the bits of the 10- or 8-bit word. for (let j = 0; j < wordBits; j++) { let bitSet = (word & (1 << (wordBits - j - 1))) > 0; if (bitSet) { bytes[curByte] |= 1 << (8 - curBit - 1); } curBit += 1; if (curBit >= 8) { // Current byte has 8 bits, go to the next byte. curByte += 1; curBit = 0; } } } return [bytes, null]; } // seedPhraseToSeed will take a seed phrase and return the corresponding seed, // providing an error if the seed phrase is invalid. This is an alias of // validSeedPhrase. function seedPhraseToSeed(seedPhrase) { return validSeedPhrase(seedPhrase); } // Define some empty values to make our return statements more concise. const nu8$1 = new Uint8Array(0); const nkp = { publicKey: nu8$1, secretKey: nu8$1 }; // computeRegistrySignature will take a secret key and the required fields of a // registry entry and use them to compute a registry signature, returning both // the signature and the encoded data for the registry entry. function computeRegistrySignature(secretKey, dataKey, data, revision) { // Check that the data is the right size. if (data.length > 86) { return [nu8$1, "registry data must be at most 86 bytes"]; } // Build the encoded data. let [encodedData, errEPB] = encodePrefixedBytes(data); if (errEPB !== null) { return [nu8$1, addContextToErr(errEPB, "unable to encode provided registry data")]; } let [encodedRevision, errEU64] = encodeU64(revision); if (errEU64 !== null) { return [nu8$1, addContextToErr(errEU64, "unable to encode the revision number")]; } // Build the signing data. let dataToSign = new Uint8Array(32 + 8 + data.length + 8); dataToSign.set(dataKey, 0); dataToSign.set(encodedData, 32); dataToSign.set(encodedRevision, 32 + 8 + data.length); let sigHash = blake2b(dataToSign); // Sign the data. let [sig, errS] = ed25519Sign(sigHash, secretKey); if (errS !== null) { return [nu8$1, addContextToErr(errS, "unable to sign registry entry")]; } return [sig, null]; } // deriveRegistryEntryID derives a registry entry ID from a provided pubkey and // datakey. function deriveRegistryEntryID(pubkey, datakey) { // Check the lengths of the inputs. if (pubkey.length !== 32) { return [nu8$1, "pubkey is invalid, length is wrong"]; } if (datakey.length !== 32) { return [nu8$1, "datakey is not a valid hash, length is wrong"]; } // Establish the encoding. First 16 bytes is a specifier, second 8 // bytes declares the length of the pubkey, the next 32 bytes is the // pubkey and the final 32 bytes is the datakey. This encoding is // determined by the Sia protocol. let encoding = new Uint8Array(16 + 8 + 32 + 32); // Set the specifier. encoding[0] = "e".charCodeAt(0); encoding[1] = "d".charCodeAt(0); encoding[2] = "2".charCodeAt(0); encoding[3] = "5".charCodeAt(0); encoding[4] = "5".charCodeAt(0); encoding[5] = "1".charCodeAt(0); encoding[6] = "9".charCodeAt(0); // Set the pubkey. let [encodedLen, errU64] = encodeU64(32n); if (errU64 !== null) { return [nu8$1, addContextToErr(errU64, "unable to encode pubkey length")]; } encoding.set(encodedLen, 16); encoding.set(pubkey, 16 + 8); encoding.set(datakey, 16 + 8 + 32); // Get the final ID by hashing the encoded data. let id = blake2b(encoding); return [id, null]; } // entryIDToSkylink converts a registry entry id to a resolver skylink. function entryIDToSkylink(entryID) { let v2Skylink = new Uint8Array(34); v2Skylink.set(entryID, 2); v2Skylink[0] = 1; return bufToB64(v2Skylink); } // resolverLink will take a registryEntryID and return the corresponding // resolver link. function resolverLink(entryID) { if (entryID.length !== 32) { return ["", "provided entry ID has the wrong length"]; } let v2Skylink = new Uint8Array(34); v2Skylink.set(entryID, 2); v2Skylink[0] = 1; let skylink = bufToB64(v2Skylink); return [skylink, null]; } // registryEntryKeys will use the user's seed to derive a keypair and a datakey // using the provided seed and tags. The keypairTag is a tag which salts the // keypair. If you change the input keypairTag, the resulting public key and // secret key will be different. The dataKey tag is the salt for the datakey, // if you provide a different datakey tag, the resulting datakey will be // different. // // Note that changing the keypair tag will also change the resulting datakey. // The purpose of the keypair tag is to obfuscate the fact that two registry // entries are owned by the same identity. This obfuscation would break if two // different public keys were using the same datakey. Changing the datakey does // not change the public key. function taggedRegistryEntryKeys(seed, keypairTagStr, datakeyTagStr) { if (seed.length !== SEED_BYTES) { return [nkp, nu8$1, "seed has the wrong length"]; } if (keypairTagStr.length > 255) { return [nkp, nu8$1, "keypairTag must be less than 256 characters"]; } // If no datakey tag was provided, use the empty string. if (datakeyTagStr === undefined) { datakeyTagStr = ""; } // Generate a unique set of entropy using the seed and keypairTag. let keypairTag = new TextEncoder().encode(keypairTagStr); let entropyInput = new Uint8Array(keypairTag.length + seed.length); entropyInput.set(seed, 0); entropyInput.set(keypairTag, seed.length); let keypairEntropy = sha512(entropyInput); // Use the seed to dervie the datakey for the registry entry. We use // a different tag to ensure that the datakey is independently random, such // that the registry entry looks like it could be any other registry entry. // // We don't want it to be possible for two different combinations of // tags to end up with the same datakey. If you don't use a length // prefix, for example the tags ["123", "456"] and ["12", "3456"] would // have the same datakey. You have to add the length prefix to the // first tag otherwise you can get pairs like ["6", "4321"] and ["65", // "321"] which could end up with the same datakey. let datakeyTag = new TextEncoder().encode(datakeyTagStr); let datakeyInput = new Uint8Array(seed.length + 1 + keypairTag.length + datakeyTag.length); let keypairLen = new Uint8Array(1); keypairLen[0] = keypairTag.length; datakeyInput.set(seed); datakeyInput.set(keypairLen, seed.length); datakeyInput.set(keypairTag, seed.length + 1); datakeyInput.set(datakeyTag, seed.length + 1 + keypairTag.length); let datakeyEntropy = sha512(datakeyInput); // Create the private key for the registry entry. let [keypair, errKPFE] = ed25519KeypairFromEntropy(keypairEntropy.slice(0, 32)); if (errKPFE !== null) { return [nkp, nu8$1, addContextToErr(errKPFE, "unable to derive keypair")]; } let datakey = datakeyEntropy.slice(0, 32); return [keypair, datakey, null]; } // verifyRegistrySignature will verify the signature of a registry entry. function verifyRegistrySignature(pubkey, datakey, data, revision, sig) { let [encodedData, errEPB] = encodePrefixedBytes(data); if (errEPB !== null) { return false; } let [encodedRevision, errU64] = encodeU64(revision); if (errU64 !== null) { return false; } let dataToVerify = new Uint8Array(32 + 8 + data.length + 8); dataToVerify.set(datakey, 0); dataToVerify.set(encodedData, 32); dataToVerify.set(encodedRevision, 32 + 8 + data.length); let sigHash = blake2b(dataToVerify); return ed25519Verify(sigHash, sig, pubkey); } // validateSkyfilePath checks whether the provided path is a valid path for a // file in a skylink. function validateSkyfilePath(path) { if (path === "") { return "path cannot be blank"; } if (path === "..") { return "path cannot be .."; } if (path === ".") { return "path cannot be ."; } if (path.startsWith("/")) { return "metdata.Filename cannot start with /"; } if (path.startsWith("../")) { return "metdata.Filename cannot start with ../"; } if (path.startsWith("./")) { return "metdata.Filename cannot start with ./"; } let pathElems = path.split("/"); for (let i = 0; i < pathElems.length; i++) { if (pathElems[i] === ".") { return "path cannot have a . element"; } if (pathElems[i] === "..") { return "path cannot have a .. element"; } if (pathElems[i] === "") { return "path cannot have an empty element, cannot contain //"; } } return null; } // validateSkyfileMetadata checks whether the provided metadata is valid // metadata for a skyfile. function validateSkyfileMetadata(metadata) { // Check that the filename is valid. if (!("Filename" in metadata)) { return "metadata.Filename does not exist"; } if (typeof metadata.Filename !== "string") { return "metadata.Filename is not a string"; } let errVSP = validateSkyfilePath(metadata.Filename); if (errVSP !== null) { return addContextToErr(errVSP, "metadata.Filename does not have a valid path"); } // Check that there are no subfiles. if ("Subfiles" in metadata) { // TODO: Fill this out using code from // skymodules.ValidateSkyfileMetadata to support subfiles. return "cannot upload files that have subfiles"; } // Check that the default path rules are being respected. if ("DisableDefaultPath" in metadata && "DefaultPath" in metadata) { return "cannot set both a DefaultPath and also DisableDefaultPath"; } if ("DefaultPath" in metadata) { // TODO: Fill this out with code from // skymodules.validateDefaultPath to support subfiles and // default paths. return "cannot set a default path if there are no subfiles"; } if ("TryFiles" in metadata) { if (!metadata.TryFiles.IsArray()) { return "metadata.TryFiles must be an array"; } if (metadata.TryFiles.length === 0) { return "metadata.TryFiles should not be empty"; } if ("DefaultPath" in metadata) { return "metadata.TryFiles cannot be used alongside DefaultPath"; } if ("DisableDefaultPath" in metadata) { return "metadata.TryFiles cannot be used alongside DisableDefaultPath"; } // TODO: finish the TryFiles checking using skymodules.ValidateTryFiles return "TryFiles is not supported at this time"; } if ("ErrorPages" in metadata) { // TODO: finish using skymodules.ValidateErrorPages return "ErrorPages is not supported at this time"; } return null; } // validSkylink returns true if the provided Uint8Array is a valid skylink. // This is an alias for 'parseSkylinkBitfield', as both perform the same // validation. function validSkylink(skylink) { if (skylink.length !== 34) { return false; } let [, , , errPSB] = parseSkylinkBitfield(skylink); if (errPSB !== null) { return false; } return true; } // Helper consts to make returning empty values alongside errors more // convenient. const nu8 = new Uint8Array(0); // verifyResolverLinkProof will check that the given resolver proof matches the // provided skylink. If the proof is correct and the signature matches, the // data will be returned. The returned link will be a verified skylink. function verifyResolverLinkProof(skylink, proof) { // Verify the presented skylink is formatted correctly. if (skylink.length !== 34) { return [nu8, "skylink is malformed, expecting 34 bytes"]; } // Verify that all of the required fields are present in the proof. if (!("data" in proof) || !("datakey" in proof) || !("publickey" in proof) || !("signature" in proof) || !("type" in proof) || !("revision" in proof)) { return [nu8, "proof is malformed, fields are missing"]; } if (!("algorithm" in proof.publickey) || !("key" in proof.publickey)) { return [nu8, "pubkey is malformed"]; } // Verify the typing of the fields. if (typeof proof.data !== "string") { return [nu8, "data is malformed"]; } let dataStr = proof.data; if (typeof proof.datakey !== "string") { return [nu8, "datakey is malformed"]; } let datakeyStr = proof.datakey; if (proof.publickey.algorithm !== "ed25519") { return [nu8, "pubkey has unrecognized algorithm"]; } if (typeof proof.publickey.key !== "string") { return [nu8, "pubkey key is malformed"]; } let pubkeyStr = proof.publickey.key; if (typeof proof.signature !== "string") { return [nu8, "signature is malformed"]; } if (proof.type !== 1n) { return [nu8, "registry entry has unrecognized type: " + tryStringify(proof.type)]; } let sigStr = proof.signature; if (typeof proof.revision !== "bigint") { return [nu8, "revision is malformed"]; } let revision = proof.revision; // Decode all of the fields. They are presented in varied types and // encodings. let [data, errD] = hexToBuf(dataStr); if (errD !== null) { return [nu8, addContextToErr(errD, "data is invalid hex")]; } let [datakey, errDK] = hexToBuf(datakeyStr); if (errDK !== null) { return [nu8, addContextToErr(errDK, "datakey is invalid hex")]; } let [pubkey, errPK] = b64ToBuf(pubkeyStr); if (errPK !== null) { return [nu8, addContextToErr(errPK, "pubkey key is invalid base64")]; } let [sig, errS] = hexToBuf(sigStr); if (errS !== null) { return [nu8, addContextToErr(errS, "signature is invalid hex")]; } // Verify that the data is a skylink - this is a proof for a resolver, // which means the proof is pointing to a specific skylink. if (!validSkylink(data)) { return [nu8, "this skylink does not resolve to another skylink"]; } // Verify that the combination of the datakey and the public key match // the skylink. let [entryID, errREID] = deriveRegistryEntryID(pubkey, datakey); if (errREID !== null) { return [nu8, addContextToErr(errREID, "proof pubkey is malformed")]; } let linkID = skylink.slice(2, 34); for (let i = 0; i < entryID.length; i++) { if (entryID[i] !== linkID[i]) { return [nu8, "proof pubkey and datakey do not match the skylink root"]; } } // Verify the signature. if (!verifyRegistrySignature(pubkey, datakey, data, revision, sig)) { return [nu8, "signature does not match"]; } return [data, null]; } // verifyResolverLinkProofs will verify a set of resolver link proofs provided // by a portal after performing a resolver link lookup. Each proof corresponds // to one level of resolution. The final value returned will be the V1 skylink // at the end of the chain. // // This function treats the proof as untrusted data and will verify all of the // fields that are provided. function verifyResolverLinkProofs(skylink, proof) { // Check that the proof is an array. if (!Array.isArray(proof)) { return [nu8, "provided proof is not an array: " + tryStringify(proof)]; } if (proof.length === 0) { return [nu8, "proof array is empty"]; } // Check each proof in the chain, returning the final skylink. for (let i = 0; i < proof.length; i++) { let errVRLP; [skylink, errVRLP] = verifyResolverLinkProof(skylink, proof[i]); if (errVRLP !== null) { return [nu8, addContextToErr(errVRLP, "one of the resolution proofs is invalid")]; } } // Though it says 'skylink', the verifier is actually just returning // whatever the registry data is. We need to check that the final value // is a V1 skylink. if (skylink.length !== 34) { return [nu8, "final value returned by the resolver link is not a skylink"]; } let [version, , , errPSB] = parseSkylinkBitfield(skylink); if (errPSB !== null) { return [nu8, addContextToErr(errPSB, "final value returned by resolver link is not a valid skylink")]; } if (version !== 1n) { return [nu8, "final value returned by resolver link is not a v1 skylink"]; } return [skylink, null]; } // Establish the function that verifies the result is correct. // // The fileDataPtr input is an empty object that verifyDownloadResponse will // fill with the fileData. It basically allows the verify function to // communicate back to the caller. Note that the verify function might be // called multiple times in a row if early portals fail to retrieve the data, // but the verify function doesn't write to the fileDataPtr until it knows that // the download is final. function verifyDownloadResponse(response, u8Link, fileDataPtr) { return new Promise((resolve) => { // Currently the only valid successful response for a download is a // 200. Anything else is unexpected and counts as an error. if (response.status !== 200) { resolve("unrecognized response status " + tryStringify(response.status) + ", expecting 200"); return; } // Break the input link into its components. let [version, offset, fetchSize, errBF] = parseSkylinkBitfield(u8Link); if (errBF !== null) { resolve(addContextToErr(errBF, "skylink bitfield could not be parsed")); return; } // If this is a resolver skylink, we need to verify the resolver // proofs. This conditional will update the value of 'u8Link' to be the // value of the fully resolved link. if (version === 2n) { // Verify the resolver proofs and update the link to the correct // link. let proofJSON = response.headers.get("skynet-proof"); if (proofJSON === null || proofJSON === undefined) { resolve("response did not include resolver proofs"); return; } let [proof, errPJ] = parseJSON(proofJSON); if (errPJ !== null) { resolve(addContextToErr(errPJ, "unable to parse resolver link proofs")); return; } // We need to update the u8Link in-place so that the rest of the // function doesn't need special handling. let errVRLP; [u8Link, errVRLP] = verifyResolverLinkProofs(u8Link, proof); if (errVRLP !== null) { resolve(addContextToErr(errVRLP, "unable to verify resolver link proofs")); return; } // We also need to update the parsed bitfield, because the link has // changed. [version, offset, fetchSize, errBF] = parseSkylinkBitfield(u8Link); if (errBF !== null) { resolve(addContextToErr(errBF, "fully resolved link has invalid bitfield")); return; } if (version !== 1n) { resolve("fully resolved link does not have version 1"); return; } } response .arrayBuffer() .then((buf) => { let [fileData, portalAtFault, errVD] = verifyDownload(u8Link.slice(2, 34), offset, fetchSize, buf); if (errVD !== null && portalAtFault) { resolve("received invalid download from portal"); return; } if (errVD !== null) { fileDataPtr.fileData = new Uint8Array(0); fileDataPtr.err = addContextToErr(errVD, "file is corrupt"); } else { fileDataPtr.fileData = fileData; fileDataPtr.err = null; } // If the portal is not at fault, we tell progressiveFetch that // the download was a success. The caller will have to check // the fileDataPtr resolve(null); }) .catch((err) => { resolve(addContextToErr(err, "unable to read response body")); }); }); } // progressiveFetchHelper is the full progressiveFetch function, split out into // a helper because the inptus/api is more complicated but only necessary for // internal use. function progressiveFetchHelper(pfm, resolve, verifyFunction) { // If we run out of portals, return an error. if (pfm.remainingPortals.length === 0) { let newLog = "query failed because all portals have been tried"; pfm.logs.push(newLog); resolve({ success: false, portal: null, response: null, portalsFailed: pfm.portalsFailed, responsesFailed: pfm.responsesFailed, messagesFailed: pfm.messagesFailed, remainingPortals: null, logs: pfm.logs, }); return; } // Grab the portal and query. let portal = pfm.remainingPortals.shift(); let query = portal + pfm.endpoint; // Create a helper function for trying the next portal. let nextPortal = function (response, log) { if (response !== null) { response .clone() .text() .then((t) => { pfm.logs.push(log); pfm.portalsFailed.push(portal); pfm.responsesFailed.push(response); pfm.messagesFailed.push(t); progressiveFetchHelper(pfm, resolve, verifyFunction); }); } else { pfm.logs.push(log); pfm.portalsFailed.push(portal); pfm.responsesFailed.push(response); pfm.messagesFailed.push(""); progressiveFetchHelper(pfm, resolve, verifyFunction); } }; // Try sending the query to the portal. fetch(query, pfm.fetchOpts) .then((response) => { // Check for a 5XX error. if (!("status" in response) || typeof response.status !== "number") { nextPortal(response, "portal has returned invalid response\n" + tryStringify({ portal, query })); return; } if (response.status < 200 || response.status >= 300) { nextPortal(response, "portal has returned error status\n" + tryStringify({ portal, query })); return; } // Check the result against the verify function. verifyFunction(response.clone()).then((errVF) => { if (errVF !== null) { nextPortal(response, "verify function has returned an error from portal " + portal + " - " + errVF); return; } // Success! Return the response. resolve({ success: true, portal, response, portalsFailed: pfm.portalsFailed, responsesFailed: pfm.responsesFailed, remainingPortals: pfm.remainingPortals, messagesFailed: pfm.messagesFailed, logs: pfm.logs, }); }); }) .catch((err) => { // This portal failed, try again with the next portal. nextPortal(null, "fetch returned an error\n" + tryStringify(err) + tryStringify(pfm.fetchOpts)); return; }); } // progressiveFetch will query multiple portals until one returns with a // non-error response. In the event of a 4XX response, progressiveFetch will // keep querying additional portals to try and find a working 2XX response. In // the event that no working 2XX response is found, the first 4XX response will // be returned. // // If progressiveFetch returns a 2XX response, it merely means that the portal // returned a 2XX response. progressiveFetch cannot be confident that the // portal has returned a correct/honest message, the verification has to be // handled by the caller. The response (progressiveFetchResult) contains the // list of portals that progressiveFetch hasn't tried yet. In the event that // the 2XX response is not correct, the progressiveFetchResult contains the // list of failover portals that have not been used yet, allowing // progressiveFetch to be called again. // // This progressive method of querying portals helps prevent queries from // failing, but if the first portal is not a good portal it introduces // substantial latency. progressiveFetch does not do anything to make sure the // portals are the best portals, it just queries them in order. The caller // should make a best attempt to always have the best, most reliable and // fastest portal as the first portal in the list. // // The reason that we don't blindly accept a 4XX response from a portal is that // we have no way of verifying that the 4XX is legitimate. We don't trust the // portal, and we can't give a rogue portal the opportunity to interrupt our // user experience simply by returning a dishonest 404. So we need to keep // querying more portals and gain confidence that the 404 a truthful response. function progressiveFetch(endpoint, fetchOpts, portals, verifyFunction) { let portalsCopy = [...portals]; return new Promise((resolve) => { let pfm = { endpoint, fetchOpts, remainingPortals: portalsCopy, portalsFailed: [], responsesFailed: [], messagesFailed: [], logs: [], }; progressiveFetchHelper(pfm, resolve, verifyFunction); }); } // downloadSkylink will download the provided skylink. function downloadSkylink(skylink) { return new Promise((resolve) => { // Get the Uint8Array of the input skylink. let [u8Link, errBTB] = b64ToBuf(skylink); if (errBTB !== null) { resolve([new Uint8Array(0), addContextToErr(errBTB, "unable to decode skylink")]); return; } if (!validSkylink(u8Link)) { resolve([new Uint8Array(0), "skylink appears to be invalid"]); return; } // Prepare the download call. let endpoint = "/skynet/trustless/basesector/" + skylink; let fileDataPtr = { fileData: new Uint8Array(0), err: null }; let verifyFunction = function (response) { return verifyDownloadResponse(response, u8Link, fileDataPtr); }; // Perform the download call. progressiveFetch(endpoint, null, defaultPortalList, verifyFunction).then((result) => { // Return an error if the call failed. if (result.success !== true) { // Check for a 404. for (let i = 0; i < result.responsesFailed.length; i++) { if (result.responsesFailed[i].status === 404) { resolve([new Uint8Array(0), "404"]); return; } } // Error is not a 404, return the logs as the error. let err = tryStringify(result.logs); resolve([new Uint8Array(0), addContextToErr(err, "unable to complete download")]); return; } // Check if the portal is honest but the download is corrupt. if (fileDataPtr.err !== null) { resolve([new Uint8Array(0), addContextToErr(fileDataPtr.err, "download is corrupt")]); return; } resolve([fileDataPtr.fileData, null]); }); }); } // verifyDecodedResp will verify the decoded response from a portal for a // regRead call. function verifyDecodedResp(resp, data, pubkey, datakey) { // Status is expected to be 200. if (resp.status !== 200) { return "expected 200 response status, got: " + tryStringify(resp.status); } // Verify that all required fields were provided. if (!("data" in data)) { return "expected data field in response"; } if (typeof data.data !== "string") { return "expected data field to be a string"; } if (!("revision" in data)) { return "expected revision in response"; } if (typeof data.revision !== "bigint") { return "expected revision to be a number"; } if (!("signature" in data)) { return "expected signature in response"; } if (typeof data.signature !== "string") { return "expected signature to be a string"; } // Parse out the fields we need. let [entryData, errHTB] = hexToBuf(data.data); if (errHTB !== null) { return "could not decode registry data from response"; } let [sig, errHTB2] = hexToBuf(data.signature); if (errHTB2 !== null) { return "could not decode signature from response"; } // Verify the signature. if (!verifyRegistrySignature(pubkey, datakey, entryData, data.revision, sig)) { return "signature mismatch"; } // TODO: Need to be handling type 2 registry entries here otherwise we will // be flagging non malicious portals as malicious. return null; } // verifyRegistryReadResponse will verify that the registry read response from // the portal was correct. function verifyRegistryReadResponse(resp, pubkey, datakey) { return new Promise((resolve) => { resp .text() .then((str) => { let [obj, errPJ] = parseJSON(str); if (errPJ !== null) { resolve(addContextToErr(errPJ, "unable to parse registry response")); return; } let errVDR = verifyDecodedResp(resp, obj, pubkey, datakey); if (errVDR !== null) { resolve(addContextToErr(errVDR, "regRead response failed verification")); return; } resolve(null); }) .catch((err) => { resolve(addContextToErr(tryStringify(err), "unable to decode response")); }); }); } // verifyRegistryWriteResponse will verify that the response from a // registryWrite call is valid. There's not much to verify beyond looking for // the right response code, as the portal is not providing us with data, just // confirming that a write succeeded. function verifyRegistryWriteResponse(resp) { return new Promise((resolve) => { if (resp.status === 204) { resolve(null); } resolve("expecting 200 status code for registry write, got:" + resp.status.toString()); }); } // stringifyjson.ts is split into a separate file to avoid a circular // dependency. If you merge it with stringifytry.ts you have a circular import // where err.js is importing stringify.js and stringify.js is importing err.js. // Splitting the functions out resolves this issue. // jsonStringify is a replacement for JSON.stringify that returns an error // rather than throwing. function jsonStringify(obj) { try { let str = JSON.stringify(obj); return [str, null]; } catch (err) { return ["", addContextToErr(tryStringify(err), "unable to stringify object")]; } } var skynet = /*#__PURE__*/Object.freeze({ __proto__: null, blake2b: blake2b, defaultPortalList: defaultPortalList, dictionary: dictionary, downloadSkylink: downloadSkylink, verifyDownload: verifyDownload, verifyDownloadResponse: verifyDownloadResponse, ed25519Sign: ed25519Sign, ed25519Verify: ed25519Verify, b64ToBuf: b64ToBuf, bufToB64: bufToB64, bufToHex: bufToHex, bufToStr: bufToStr, encodePrefixedBytes: encodePrefixedBytes, encodeU64: encodeU64, hexToBuf: hexToBuf, addContextToErr: addContextToErr, composeErr: composeErr, blake2bAddLeafBytesToProofStack: blake2bAddLeafBytesToProofStack, blake2bMerkleRoot: blake2bMerkleRoot, blake2bProofStackRoot: blake2bProofStackRoot, parseJSON: parseJSON, progressiveFetch: progressiveFetch, computeRegistrySignature: computeRegistrySignature, deriveRegistryEntryID: deriveRegistryEntryID, entryIDToSkylink: entryIDToSkylink, resolverLink: resolverLink, taggedRegistryEntryKeys: taggedRegistryEntryKeys, verifyRegistrySignature: verifyRegistrySignature, verifyRegistryReadResponse: verifyRegistryReadResponse, verifyRegistryWriteResponse: verifyRegistryWriteResponse, deriveChildSeed: deriveChildSeed, deriveMyskyRootKeypair: deriveMyskyRootKeypair, generateSeedPhraseDeterministic: generateSeedPhraseDeterministic, seedPhraseToSeed: seedPhraseToSeed, validSeedPhrase: validSeedPhrase, sha512: sha512, parseSkylinkBitfield: parseSkylinkBitfield, skylinkV1Bitfield: skylinkV1Bitfield, validateSkyfileMetadata: validateSkyfileMetadata, validateSkyfilePath: validateSkyfilePath, validSkylink: validSkylink, verifyResolverLinkProofs: verifyResolverLinkProofs, jsonStringify: jsonStringify, tryStringify: tryStringify }); // @ts-ignore window.kernel = kernel; // @ts-ignore window.skynet = skynet; })();