diff --git a/dist/index.d.ts b/dist/index.d.ts index 140eddc..6b6f74b 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,10 +1,7 @@ import { Page } from "puppeteer"; 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"; -export declare function generateSeedPhrase(): string; -export declare function login(page: Page, seed?: string): Promise; +export declare function generateSeedPhrase(): Uint8Array; +export declare function login(page: Page, seed?: Uint8Array): Promise; export declare function loadTester(page: Page, port?: number): Promise; declare class Tester { private page; diff --git a/dist/index.js b/dist/index.js index 794c217..c63a4d0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,129 +1,53 @@ -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 { bufToHex } from "libskynet"; import * as path from "path"; -import { overwriteRegistryEntry } from "libskynetnode"; import * as kernel from "libkernel"; -import { webcrypto } from "crypto"; +import * as ed from "@noble/ed25519"; // @ts-ignore 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 = 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 - // of numbers that would produce no bias. Because the search space - // (1024) evenly divides the random number space (2^16), we can skip - // this step and just use a modulus instead. The result will have no - // bias, but only because the search space is a power of 2. - let randNums = new Uint16Array(SEED_ENTROPY_WORDS); - crypto.getRandomValues(randNums); - // Generate the seed phrase from the randNums. - let seedWords = []; - for (let i = 0; i < SEED_ENTROPY_WORDS; i++) { - 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] = seedToChecksumWords(seed); - // Assemble the final seed phrase and set the text field. - return [...seedWords, checksumOne, checksumTwo].join(" "); -} -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]; + return ed.utils.randomPrivateKey(); } export async function login(page, seed = generateSeedPhrase()) { - await page.goto("http://skt.us"); - let userSeed; - [userSeed] = seedPhraseToSeed(seed); - let seedHex = bufToHex(userSeed); - await page.evaluate((seed) => { - window.localStorage.setItem("v1-seed", seed); - }, seedHex); - let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2"); - // Get the registry keys. - let [keypair, dataKey] = taggedRegistryEntryKeys(kernelEntrySeed, "user kernel"); - await overwriteRegistryEntry(keypair, dataKey, b64ToBuf(TEST_KERNEL_SKLINK)[0]); + await page.goto("http://kernel.lumeweb.com"); + let userSeed = seed; + let seedHex = bufToHex(userSeed); + await page.evaluate((seed) => { + window.localStorage.setItem("v1-key", seed); + }, seedHex); } -export async function loadTester(page, port = 8080) { - const server = new StaticServer({ - rootPath: path.resolve(__dirname, "..", "public"), - port, - host: "localhost", - }); - await new Promise((resolve) => { - server.start(resolve); - }); - const stop = () => server.stop(); - process.on("SIGTERM", stop); - page.browser().on("disconnected", stop); - await page.goto(`http://localhost:${port}/`); - await page.evaluate(() => { - return kernel.init(); - }); +export async function loadTester(page, port = 8081) { + const server = new StaticServer({ + rootPath: path.resolve(__dirname, "..", "public"), + port, + host: "localhost", + }); + await new Promise((resolve) => { + server.start(resolve); + }); + const stop = () => server.stop(); + process.on("SIGTERM", stop); + page.browser().on("disconnected", stop); + await page.goto(`http://localhost:${port}/`); + await page.evaluate(() => { + return kernel.init(); + }); } class Tester { - page; - constructor(page) { - this.page = page; - } - async callModule(id, method, data = {}) { - return this.page.evaluate(async (id, method, data) => { - return kernel.callModule(id, method, data); - }, id, method, data); - } + page; + constructor(page) { + this.page = page; + } + async callModule(id, method, data = {}) { + return this.page.evaluate( + async (id, method, data) => { + return kernel.callModule(id, method, data); + }, + id, + method, + data + ); + } } export const tester = (page) => new Tester(page); diff --git a/package.json b/package.json index 2825ad6..f52bfcd 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,12 @@ "main": "dist/index.js", "type": "module", "dependencies": { + "@noble/ed25519": "^1.7.1", "libkernel": "^0.1.41", "libskynet": "^0.0.48", "libskynetnode": "^0.1.3", - "static-server": "^2.2.1", - "puppeteer": "^15.4.0" + "puppeteer": "^15.4.0", + "static-server": "^2.2.1" }, "devDependencies": { "@rollup/plugin-commonjs": "^22.0.1", diff --git a/public/tester.js b/public/tester.js index ccb02f5..83ee260 100644 --- a/public/tester.js +++ b/public/tester.js @@ -170,7 +170,7 @@ } // Create the queryMap. - let queries = {}; + const 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 @@ -194,15 +194,15 @@ // 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; + const nonceNum = nonceCounter; nonceCounter += 1; - let [nonceNumBytes, err] = encodeU64$1(BigInt(nonceNum)); + const [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); + const noncePreimage = new Uint8Array(nonceNumBytes.length + nonceSeed.length); noncePreimage.set(nonceNumBytes, 0); noncePreimage.set(nonceSeed, nonceNumBytes.length); return bufToB64$1(noncePreimage); @@ -210,9 +210,9 @@ // 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 + // approved sources are kernel.lumeweb.com and the browser extension bridge (which has // an event.source equal to 'window') - if (event.source !== window && event.origin !== "https://skt.us") { + if (event.source !== window && event.origin !== "https://kernel.lumeweb.com") { return; } // Ignore any messages that don't have a method and data field. @@ -289,7 +289,7 @@ if (!(event.data.nonce in queries)) { return; } - let query = queries[event.data.nonce]; + const 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") { @@ -314,19 +314,19 @@ } // 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 + // launchKernelFrame will launch the kernel.lumeweb.com 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"; + const iframe = document.createElement("iframe"); + iframe.src = "https://kernel.lumeweb.com"; 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"; + kernelOrigin = "https://kernel.lumeweb.com"; + kernelAuthLocation = "https://kernel.lumeweb.com/auth.html"; sourceResolve(); // Set a timer to fail the login process if the kernel doesn't load in // time. @@ -340,7 +340,7 @@ } // 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. + // messageBridge will open an iframe to kernel.lumeweb.com and use that as the kernel. let kernelSource; let kernelOrigin; let kernelAuthLocation; @@ -348,7 +348,7 @@ // 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) => { + const p = new Promise((resolve) => { bridgeResolve = resolve; }); p.then(([, err]) => { @@ -372,7 +372,7 @@ sourceResolve(); }); // Add the handler to the queries map. - let nonce = nextNonce(); + const nonce = nextNonce(); queries[nonce] = { resolve: bridgeResolve, }; @@ -391,7 +391,7 @@ return; } bridgeInitComplete = true; - log("browser extension not found, falling back to skt.us"); + log("browser extension not found, falling back to kernel.lumeweb.com"); launchKernelFrame(); }, 500); return initPromise; @@ -457,12 +457,12 @@ // 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 = { + const moduleCallData = { module, method, data, }; - let [, query] = newKernelQuery("moduleCall", moduleCallData, false); + const [, query] = newKernelQuery("moduleCall", moduleCallData, false); return query; } // connectModule is the standard function to send a query to a module that can @@ -485,7 +485,7 @@ // 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 = { + const moduleCallData = { module, method, data, @@ -548,7 +548,7 @@ // 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) => { + const getNonce = new Promise((resolve) => { init().then(() => { kernelLoadedPromise.then(() => { resolve(nextNonce()); @@ -560,7 +560,7 @@ // 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) => { + const haveQueryCreated = new Promise((queryCreatedResolve) => { p = new Promise((resolve) => { getNonce.then((nonce) => { queries[nonce] = { resolve }; @@ -575,7 +575,7 @@ // 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) => { + const getReadyForKernelNonce = new Promise((resolve) => { readyForKernelNonce = resolve; }); // Create the sendUpdate function. It defaults to doing nothing. After the @@ -594,7 +594,7 @@ // // 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) => { + const blockForKernelNonce = new Promise((resolve) => { haveQueryCreated.then((nonce) => { queries[nonce]["kernelNonceReceived"] = resolve; readyForKernelNonce(); @@ -620,14 +620,14 @@ 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 = { + // we are talking to kernel.lumeweb.com or the background script. + const kernelMessage = { method, nonce, data, sendKernelNonce: sendUpdates, }; - let backgroundMessage = { + const backgroundMessage = { method: "newKernelQuery", nonce, data: kernelMessage, @@ -635,7 +635,7 @@ // 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") { + if (kernelOrigin === "https://kernel.lumeweb.com") { kernelSource.postMessage(kernelMessage, kernelOrigin); } else { @@ -713,8 +713,8 @@ // prevents the portal from lying about the download. function download(skylink) { return new Promise((resolve) => { - let downloadModule = "AQCIaQ0P-r6FwPEDq3auCZiuH_jqrHfqRcY7TjZ136Z_Yw"; - let data = { + const downloadModule = "AQCIaQ0P-r6FwPEDq3auCZiuH_jqrHfqRcY7TjZ136Z_Yw"; + const data = { skylink, }; callModule(downloadModule, "secureDownload", data).then(([result, err]) => { @@ -737,8 +737,8 @@ // less required. function registryRead(publicKey, dataKey) { return new Promise((resolve) => { - let registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q"; - let data = { + const registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q"; + const data = { publicKey, dataKey, }; @@ -765,8 +765,8 @@ // 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 = { + const registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q"; + const callData = { publicKey: keypair.publicKey, secretKey: keypair.secretKey, dataKey, @@ -791,8 +791,8 @@ function upload(filename, fileData) { return new Promise((resolve) => { // Prepare the module call. - let uploadModule = "AQAT_a0MzOInZoJzt1CwBM2U8oQ3GIfP5yKKJu8Un-SfNg"; - let data = { + const uploadModule = "AQAT_a0MzOInZoJzt1CwBM2U8oQ3GIfP5yKKJu8Un-SfNg"; + const data = { filename, fileData, }; @@ -813,7 +813,7 @@ // distribution of the kernel". function kernelVersion() { return new Promise((resolve) => { - let [, query] = newKernelQuery("version", {}, false); + const [, query] = newKernelQuery("version", {}, false); query.then(([result, err]) => { if (err !== null) { resolve(["", "", err]); @@ -840,7 +840,8 @@ init: init, newKernelQuery: newKernelQuery, addContextToErr: addContextToErr$1, - checkObj: checkObj + checkObj: checkObj, + objAsString: objAsString }); // Blake2B, adapted from the reference implementation in RFC7693 @@ -5219,5 +5220,15 @@ window.kernel = kernel; // @ts-ignore window.skynet = skynet; + window.addEventListener("message", (event) => { + const data = event.data?.data; + if (event.data.method === "log") { + if (data?.isErr === false) { + console.log(data.message); + return; + } + console.error(data.message); + } + }); })(); diff --git a/src/index.ts b/src/index.ts index 40818f9..4e8aeff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,12 +6,11 @@ import { 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"; +import * as ed from "@noble/ed25519"; // @ts-ignore import StaticServer from "static-server"; import { Page } from "puppeteer"; @@ -19,118 +18,20 @@ import { errTuple } from "libskynet"; 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 = webcrypto as unknown as Crypto; - 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 - // of numbers that would produce no bias. Because the search space - // (1024) evenly divides the random number space (2^16), we can skip - // this step and just use a modulus instead. The result will have no - // bias, but only because the search space is a power of 2. - let randNums = new Uint16Array(SEED_ENTROPY_WORDS); - crypto.getRandomValues(randNums); - // Generate the seed phrase from the randNums. - let seedWords = []; - for (let i = 0; i < SEED_ENTROPY_WORDS; i++) { - 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] = seedToChecksumWords( - seed as Uint8Array - ); - // Assemble the final seed phrase and set the text field. - return [...seedWords, checksumOne, checksumTwo].join(" "); -} - -function seedWordsToSeed(seedWords: string[]) { - // 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]; + return ed.utils.randomPrivateKey(); } export async function login(page: Page, seed = generateSeedPhrase()) { - await page.goto("http://skt.us"); + await page.goto("http://kernel.lumeweb.com"); - let userSeed: Uint8Array; + let userSeed = seed; - [userSeed] = seedPhraseToSeed(seed); let seedHex = bufToHex(userSeed); await page.evaluate((seed: string) => { - window.localStorage.setItem("v1-seed", seed); + window.localStorage.setItem("v1-key", seed); }, seedHex); - - let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2"); - - // Get the registry keys. - let [keypair, dataKey] = taggedRegistryEntryKeys( - kernelEntrySeed, - "user kernel" - ); - - await overwriteRegistryEntry( - keypair, - dataKey, - b64ToBuf(TEST_KERNEL_SKLINK)[0] - ); } export async function loadTester(page: Page, port = 8080) { diff --git a/src/tester.ts b/src/tester.ts index c7eb6fb..def3b3c 100644 --- a/src/tester.ts +++ b/src/tester.ts @@ -5,3 +5,14 @@ import * as skynet from "libskynet"; window.kernel = kernel; // @ts-ignore window.skynet = skynet; + +window.addEventListener("message", (event) => { + const data = event.data?.data; + if (event.data.method === "log") { + if (data?.isErr === false) { + console.log(data.message); + return; + } + console.error(data.message); + } +});