From 994ee4cd1f35d3133ea358928240f5d4e2a91d92 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Wed, 20 Jul 2022 14:06:18 -0400 Subject: [PATCH] *Update builds --- bin/sandbox.js | 12 +- dist/index.d.ts | 2 +- dist/index.js | 65 +- public/tester.d.ts | 1 + public/tester.js | 10744 ++++++++++++++++++++++--------------------- 5 files changed, 5561 insertions(+), 5263 deletions(-) create mode 100644 public/tester.d.ts diff --git a/bin/sandbox.js b/bin/sandbox.js index b1f10e8..37c16f5 100644 --- a/bin/sandbox.js +++ b/bin/sandbox.js @@ -1,15 +1,13 @@ #!/usr/bin/env node -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); // @ts-ignore -const index_js_1 = require("../dist/index.js"); -const puppeteer_1 = require("puppeteer"); +import { loadTester, login } from "../dist/index.js"; +import puppeteer from "puppeteer"; let browser; (async () => { - browser = await puppeteer_1.default.launch({ headless: false, devtools: true }); + browser = await puppeteer.launch({ headless: false, devtools: true }); const page = (await browser.pages()).pop(); - await (0, index_js_1.login)(page); - await (0, index_js_1.loadTester)(page); + await login(page); + await loadTester(page); })(); process.on("SIGTERM", async () => { await browser.close(); diff --git a/dist/index.d.ts b/dist/index.d.ts index ce7d30c..140eddc 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,5 +1,5 @@ import { Page } from "puppeteer"; -import { errTuple } from "libskynet/dist"; +import { errTuple } from "libskynet"; export declare const KERNEL_TEST_SUITE = "AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g"; export declare const KERNEL_HELPER_MODULE = "AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA"; export declare const TEST_KERNEL_SKLINK = "AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg"; diff --git a/dist/index.js b/dist/index.js index e3c6e3b..794c217 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,21 +1,20 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.tester = exports.loadTester = exports.login = exports.generateSeedPhrase = exports.TEST_KERNEL_SKLINK = exports.KERNEL_HELPER_MODULE = exports.KERNEL_TEST_SUITE = void 0; -const libskynet_1 = require("libskynet"); -const seed_1 = require("libskynet/dist/seed"); -const dictionary_1 = require("libskynet/dist/dictionary"); -const path = require("path"); -const libskynetnode_1 = require("libskynetnode"); -const kernel = require("libkernel"); -const crypto_1 = require("crypto"); +import { b64ToBuf, bufToHex, deriveChildSeed, dictionary, seedPhraseToSeed, taggedRegistryEntryKeys, } from "libskynet"; +import { SEED_BYTES, seedToChecksumWords } from "libskynet/dist/seed.js"; +import { DICTIONARY_UNIQUE_PREFIX } from "libskynet/dist/dictionary.js"; +import * as path from "path"; +import { overwriteRegistryEntry } from "libskynetnode"; +import * as kernel from "libkernel"; +import { webcrypto } from "crypto"; // @ts-ignore -const StaticServer = require("static-server"); -exports.KERNEL_TEST_SUITE = "AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g"; -exports.KERNEL_HELPER_MODULE = "AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA"; -exports.TEST_KERNEL_SKLINK = "AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg"; +import StaticServer from "static-server"; +import * as url from "url"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +export const KERNEL_TEST_SUITE = "AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g"; +export const KERNEL_HELPER_MODULE = "AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA"; +export const TEST_KERNEL_SKLINK = "AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg"; const SEED_ENTROPY_WORDS = 13; -const crypto = crypto_1.webcrypto; -function generateSeedPhrase() { +const crypto = webcrypto; +export function generateSeedPhrase() { // Get the random numbers for the seed phrase. Typically, you need to // have code that avoids bias by checking the random results and // re-rolling the random numbers if the result is outside of the range @@ -28,17 +27,16 @@ function generateSeedPhrase() { // Generate the seed phrase from the randNums. let seedWords = []; for (let i = 0; i < SEED_ENTROPY_WORDS; i++) { - let wordIndex = randNums[i] % libskynet_1.dictionary.length; - seedWords.push(libskynet_1.dictionary[wordIndex]); + let wordIndex = randNums[i] % dictionary.length; + seedWords.push(dictionary[wordIndex]); } // Convert the seedWords to a seed. let [seed] = seedWordsToSeed(seedWords); // Compute the checksum. - let [checksumOne, checksumTwo, err2] = (0, seed_1.seedToChecksumWords)(seed); + let [checksumOne, checksumTwo, err2] = seedToChecksumWords(seed); // Assemble the final seed phrase and set the text field. return [...seedWords, checksumOne, checksumTwo].join(" "); } -exports.generateSeedPhrase = generateSeedPhrase; function seedWordsToSeed(seedWords) { // Input checking. if (seedWords.length !== SEED_ENTROPY_WORDS) { @@ -48,15 +46,15 @@ function seedWordsToSeed(seedWords) { ]; } // We are getting 16 bytes of entropy. - let bytes = new Uint8Array(seed_1.SEED_BYTES); + 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 < libskynet_1.dictionary.length; j++) { - if (seedWords[i].slice(0, dictionary_1.DICTIONARY_UNIQUE_PREFIX) === - libskynet_1.dictionary[j].slice(0, dictionary_1.DICTIONARY_UNIQUE_PREFIX)) { + 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; } @@ -87,21 +85,20 @@ function seedWordsToSeed(seedWords) { } return [bytes, null]; } -async function login(page, seed = generateSeedPhrase()) { +export async function login(page, seed = generateSeedPhrase()) { await page.goto("http://skt.us"); let userSeed; - [userSeed] = (0, libskynet_1.seedPhraseToSeed)(seed); - let seedHex = (0, libskynet_1.bufToHex)(userSeed); + [userSeed] = seedPhraseToSeed(seed); + let seedHex = bufToHex(userSeed); await page.evaluate((seed) => { window.localStorage.setItem("v1-seed", seed); }, seedHex); - let kernelEntrySeed = (0, libskynet_1.deriveChildSeed)(userSeed, "userPreferredKernel2"); + let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2"); // Get the registry keys. - let [keypair, dataKey] = (0, libskynet_1.taggedRegistryEntryKeys)(kernelEntrySeed, "user kernel"); - await (0, libskynetnode_1.overwriteRegistryEntry)(keypair, dataKey, (0, libskynet_1.b64ToBuf)(exports.TEST_KERNEL_SKLINK)[0]); + let [keypair, dataKey] = taggedRegistryEntryKeys(kernelEntrySeed, "user kernel"); + await overwriteRegistryEntry(keypair, dataKey, b64ToBuf(TEST_KERNEL_SKLINK)[0]); } -exports.login = login; -async function loadTester(page, port = 8080) { +export async function loadTester(page, port = 8080) { const server = new StaticServer({ rootPath: path.resolve(__dirname, "..", "public"), port, @@ -118,7 +115,6 @@ async function loadTester(page, port = 8080) { return kernel.init(); }); } -exports.loadTester = loadTester; class Tester { page; constructor(page) { @@ -130,5 +126,4 @@ class Tester { }, id, method, data); } } -const tester = (page) => new Tester(page); -exports.tester = tester; +export const tester = (page) => new Tester(page); diff --git a/public/tester.d.ts b/public/tester.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/public/tester.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/public/tester.js b/public/tester.js index 92cbf94..3b39269 100644 --- a/public/tester.js +++ b/public/tester.js @@ -1,5257 +1,5561 @@ (function () { - 'use strict'; + "use strict"; - var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + // 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); + } - function getAugmentedNamespace(n) { - var f = n.default; - if (typeof f == "function") { - var a = function () { - return f.apply(this, arguments); - }; - a.prototype = f.prototype; - } else a = {}; - Object.defineProperty(a, '__esModule', {value: true}); - Object.keys(n).forEach(function (k) { - var d = Object.getOwnPropertyDescriptor(n, k); - Object.defineProperty(a, k, d.get ? d : { - enumerable: true, - get: function () { - return n[k]; - } - }); - }); - return a; - } + // 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]"; + } + } - var tester = {}; + // 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); + } - // 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); - } + 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]; + } - // 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]"; - } - } + 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, + ]); - // 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); - } + // 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; + } - 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]; - } + // 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]; + } - 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, - ]); + // 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"); + }); + } - // 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; - } + // 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]); + } + ); + }); + } - // 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]; - } + // 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]); + } + ); + }); + } - // 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"); - }); - } + // 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]); + }); + }); + } - // 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]); - }); - }); - } + // 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]); + }); + }); + } - // 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]); - }); - }); - } + 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, + }); - // 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]); - }); - }); - } + // 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); + }; - // 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]); - }); - }); - } + const defaultPortalList = ["https://siasky.net", "https://web3portal.com"]; - var dist$1 = /*#__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 - }); + // 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", + ]; - var require$$0 = /*@__PURE__*/getAugmentedNamespace(dist$1); + // 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]"; + } + } - // 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); - }; + // 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; + } - const defaultPortalList = ["https://siasky.net", "https://web3portal.com"]; + // 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]; + } - // 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", - ]; + // 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; + } - // 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]"; - } - } + // 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]; + } - // 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; - } + // @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 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 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]; + } - // 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; - } + 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; + } - // 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]; - } + 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; + } - // @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)]; - } - } + // 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); + } - // 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]; - } + // 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); + } - 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; - } + // 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; + } - 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; - } + // 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]; + } - // 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); - } + // 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")); + }); + }); + } - // 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); - } + // 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); + }); + } - // 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; - } + // 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]); + } + ); + }); + } - // 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]; - } + // 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() + ); + }); + } - // 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 dist = /*#__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 - }); - - var require$$1 = /*@__PURE__*/getAugmentedNamespace(dist); - - Object.defineProperty(tester, "__esModule", { value: true }); - const kernel = require$$0; - const skynet = require$$1; - // @ts-ignore - commonjsGlobal.kernel = kernel; - // @ts-ignore - commonjsGlobal.skynet = skynet; - - return tester; + // 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 = kernel; })();