*WIP
This commit is contained in:
parent
1cdd3ff62b
commit
2e5a28a3a9
|
@ -1,10 +1,7 @@
|
||||||
import { Page } from "puppeteer";
|
import { Page } from "puppeteer";
|
||||||
import { errTuple } from "libskynet";
|
import { errTuple } from "libskynet";
|
||||||
export declare const KERNEL_TEST_SUITE = "AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g";
|
export declare function generateSeedPhrase(): Uint8Array;
|
||||||
export declare const KERNEL_HELPER_MODULE = "AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA";
|
export declare function login(page: Page, seed?: Uint8Array): Promise<void>;
|
||||||
export declare const TEST_KERNEL_SKLINK = "AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg";
|
|
||||||
export declare function generateSeedPhrase(): string;
|
|
||||||
export declare function login(page: Page, seed?: string): Promise<void>;
|
|
||||||
export declare function loadTester(page: Page, port?: number): Promise<void>;
|
export declare function loadTester(page: Page, port?: number): Promise<void>;
|
||||||
declare class Tester {
|
declare class Tester {
|
||||||
private page;
|
private page;
|
||||||
|
|
|
@ -1,104 +1,23 @@
|
||||||
import { b64ToBuf, bufToHex, deriveChildSeed, dictionary, seedPhraseToSeed, taggedRegistryEntryKeys, } from "libskynet";
|
import { bufToHex } 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 * as path from "path";
|
||||||
import { overwriteRegistryEntry } from "libskynetnode";
|
|
||||||
import * as kernel from "libkernel";
|
import * as kernel from "libkernel";
|
||||||
import { webcrypto } from "crypto";
|
import * as ed from "@noble/ed25519";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import StaticServer from "static-server";
|
import StaticServer from "static-server";
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.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() {
|
export function generateSeedPhrase() {
|
||||||
// Get the random numbers for the seed phrase. Typically, you need to
|
return ed.utils.randomPrivateKey();
|
||||||
// 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];
|
|
||||||
}
|
}
|
||||||
export async function login(page, seed = generateSeedPhrase()) {
|
export async function login(page, seed = generateSeedPhrase()) {
|
||||||
await page.goto("http://skt.us");
|
await page.goto("http://kernel.lumeweb.com");
|
||||||
let userSeed;
|
let userSeed = seed;
|
||||||
[userSeed] = seedPhraseToSeed(seed);
|
|
||||||
let seedHex = bufToHex(userSeed);
|
let seedHex = bufToHex(userSeed);
|
||||||
await page.evaluate((seed) => {
|
await page.evaluate((seed) => {
|
||||||
window.localStorage.setItem("v1-seed", seed);
|
window.localStorage.setItem("v1-key", seed);
|
||||||
}, seedHex);
|
}, 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, port = 8080) {
|
export async function loadTester(page, port = 8081) {
|
||||||
const server = new StaticServer({
|
const server = new StaticServer({
|
||||||
rootPath: path.resolve(__dirname, "..", "public"),
|
rootPath: path.resolve(__dirname, "..", "public"),
|
||||||
port,
|
port,
|
||||||
|
@ -121,9 +40,14 @@ class Tester {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
}
|
}
|
||||||
async callModule(id, method, data = {}) {
|
async callModule(id, method, data = {}) {
|
||||||
return this.page.evaluate(async (id, method, data) => {
|
return this.page.evaluate(
|
||||||
|
async (id, method, data) => {
|
||||||
return kernel.callModule(id, method, data);
|
return kernel.callModule(id, method, data);
|
||||||
}, id, method, data);
|
},
|
||||||
|
id,
|
||||||
|
method,
|
||||||
|
data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const tester = (page) => new Tester(page);
|
export const tester = (page) => new Tester(page);
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@noble/ed25519": "^1.7.1",
|
||||||
"libkernel": "^0.1.41",
|
"libkernel": "^0.1.41",
|
||||||
"libskynet": "^0.0.48",
|
"libskynet": "^0.0.48",
|
||||||
"libskynetnode": "^0.1.3",
|
"libskynetnode": "^0.1.3",
|
||||||
"static-server": "^2.2.1",
|
"puppeteer": "^15.4.0",
|
||||||
"puppeteer": "^15.4.0"
|
"static-server": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^22.0.1",
|
"@rollup/plugin-commonjs": "^22.0.1",
|
||||||
|
|
|
@ -170,7 +170,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the queryMap.
|
// Create the queryMap.
|
||||||
let queries = {};
|
const queries = {};
|
||||||
// Define the nonce handling. nonceSeed is 16 random bytes that get generated
|
// Define the nonce handling. nonceSeed is 16 random bytes that get generated
|
||||||
// at init and serve as the baseline for creating random nonces. nonceCounter
|
// 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
|
// 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
|
// code running in the same webpage, so we don't need to hash our nonceSeed. We
|
||||||
// just need it to be unique, not undetectable.
|
// just need it to be unique, not undetectable.
|
||||||
function nextNonce() {
|
function nextNonce() {
|
||||||
let nonceNum = nonceCounter;
|
const nonceNum = nonceCounter;
|
||||||
nonceCounter += 1;
|
nonceCounter += 1;
|
||||||
let [nonceNumBytes, err] = encodeU64$1(BigInt(nonceNum));
|
const [nonceNumBytes, err] = encodeU64$1(BigInt(nonceNum));
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
// encodeU64 only fails if nonceNum is outside the bounds of a
|
// encodeU64 only fails if nonceNum is outside the bounds of a
|
||||||
// uint64, which shouldn't happen ever.
|
// uint64, which shouldn't happen ever.
|
||||||
logErr("encodeU64 somehow failed", err);
|
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(nonceNumBytes, 0);
|
||||||
noncePreimage.set(nonceSeed, nonceNumBytes.length);
|
noncePreimage.set(nonceSeed, nonceNumBytes.length);
|
||||||
return bufToB64$1(noncePreimage);
|
return bufToB64$1(noncePreimage);
|
||||||
|
@ -210,9 +210,9 @@
|
||||||
// Establish the handler for incoming messages.
|
// Establish the handler for incoming messages.
|
||||||
function handleMessage(event) {
|
function handleMessage(event) {
|
||||||
// Ignore all messages that aren't from approved kernel sources. The two
|
// 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')
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
// Ignore any messages that don't have a method and data field.
|
// Ignore any messages that don't have a method and data field.
|
||||||
|
@ -289,7 +289,7 @@
|
||||||
if (!(event.data.nonce in queries)) {
|
if (!(event.data.nonce in queries)) {
|
||||||
return;
|
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
|
// Handle a response. Once the response has been received, it is safe to
|
||||||
// delete the query from the queries map.
|
// delete the query from the queries map.
|
||||||
if (event.data.method === "response") {
|
if (event.data.method === "response") {
|
||||||
|
@ -314,19 +314,19 @@
|
||||||
}
|
}
|
||||||
// Ignore any other messages as they might be from other applications.
|
// 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.
|
// Skynet kernel if the kernel cannot be reached through the browser extension.
|
||||||
function launchKernelFrame() {
|
function launchKernelFrame() {
|
||||||
let iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
iframe.src = "https://skt.us";
|
iframe.src = "https://kernel.lumeweb.com";
|
||||||
iframe.width = "0";
|
iframe.width = "0";
|
||||||
iframe.height = "0";
|
iframe.height = "0";
|
||||||
iframe.style.border = "0";
|
iframe.style.border = "0";
|
||||||
iframe.style.position = "absolute";
|
iframe.style.position = "absolute";
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
kernelSource = iframe.contentWindow;
|
kernelSource = iframe.contentWindow;
|
||||||
kernelOrigin = "https://skt.us";
|
kernelOrigin = "https://kernel.lumeweb.com";
|
||||||
kernelAuthLocation = "https://skt.us/auth.html";
|
kernelAuthLocation = "https://kernel.lumeweb.com/auth.html";
|
||||||
sourceResolve();
|
sourceResolve();
|
||||||
// Set a timer to fail the login process if the kernel doesn't load in
|
// Set a timer to fail the login process if the kernel doesn't load in
|
||||||
// time.
|
// time.
|
||||||
|
@ -340,7 +340,7 @@
|
||||||
}
|
}
|
||||||
// messageBridge will send a message to the bridge of the skynet extension to
|
// 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,
|
// 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 kernelSource;
|
||||||
let kernelOrigin;
|
let kernelOrigin;
|
||||||
let kernelAuthLocation;
|
let kernelAuthLocation;
|
||||||
|
@ -348,7 +348,7 @@
|
||||||
// Establish the function that will handle the bridge's response.
|
// Establish the function that will handle the bridge's response.
|
||||||
let bridgeInitComplete = false;
|
let bridgeInitComplete = false;
|
||||||
let bridgeResolve = () => { }; // Need to set bridgeResolve here to make tsc happy
|
let bridgeResolve = () => { }; // Need to set bridgeResolve here to make tsc happy
|
||||||
let p = new Promise((resolve) => {
|
const p = new Promise((resolve) => {
|
||||||
bridgeResolve = resolve;
|
bridgeResolve = resolve;
|
||||||
});
|
});
|
||||||
p.then(([, err]) => {
|
p.then(([, err]) => {
|
||||||
|
@ -372,7 +372,7 @@
|
||||||
sourceResolve();
|
sourceResolve();
|
||||||
});
|
});
|
||||||
// Add the handler to the queries map.
|
// Add the handler to the queries map.
|
||||||
let nonce = nextNonce();
|
const nonce = nextNonce();
|
||||||
queries[nonce] = {
|
queries[nonce] = {
|
||||||
resolve: bridgeResolve,
|
resolve: bridgeResolve,
|
||||||
};
|
};
|
||||||
|
@ -391,7 +391,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bridgeInitComplete = true;
|
bridgeInitComplete = true;
|
||||||
log("browser extension not found, falling back to skt.us");
|
log("browser extension not found, falling back to kernel.lumeweb.com");
|
||||||
launchKernelFrame();
|
launchKernelFrame();
|
||||||
}, 500);
|
}, 500);
|
||||||
return initPromise;
|
return initPromise;
|
||||||
|
@ -457,12 +457,12 @@
|
||||||
// callModule can only be used for query-response communication, there is no
|
// callModule can only be used for query-response communication, there is no
|
||||||
// support for sending or receiving updates.
|
// support for sending or receiving updates.
|
||||||
function callModule(module, method, data) {
|
function callModule(module, method, data) {
|
||||||
let moduleCallData = {
|
const moduleCallData = {
|
||||||
module,
|
module,
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
let [, query] = newKernelQuery("moduleCall", moduleCallData, false);
|
const [, query] = newKernelQuery("moduleCall", moduleCallData, false);
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
// connectModule is the standard function to send a query to a module that can
|
// 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
|
// resolve when the module sends a response message, and works the same as the
|
||||||
// return value of callModule.
|
// return value of callModule.
|
||||||
function connectModule(module, method, data, receiveUpdate) {
|
function connectModule(module, method, data, receiveUpdate) {
|
||||||
let moduleCallData = {
|
const moduleCallData = {
|
||||||
module,
|
module,
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
|
@ -548,7 +548,7 @@
|
||||||
// Create a promise that will resolve once the nonce is available. We
|
// Create a promise that will resolve once the nonce is available. We
|
||||||
// cannot get the nonce until init() is complete. getNonce therefore
|
// cannot get the nonce until init() is complete. getNonce therefore
|
||||||
// implies that init is complete.
|
// implies that init is complete.
|
||||||
let getNonce = new Promise((resolve) => {
|
const getNonce = new Promise((resolve) => {
|
||||||
init().then(() => {
|
init().then(() => {
|
||||||
kernelLoadedPromise.then(() => {
|
kernelLoadedPromise.then(() => {
|
||||||
resolve(nextNonce());
|
resolve(nextNonce());
|
||||||
|
@ -560,7 +560,7 @@
|
||||||
// kernel provides a 'response' message. The other is for internal use and
|
// kernel provides a 'response' message. The other is for internal use and
|
||||||
// will resolve once the query has been created.
|
// will resolve once the query has been created.
|
||||||
let p;
|
let p;
|
||||||
let haveQueryCreated = new Promise((queryCreatedResolve) => {
|
const haveQueryCreated = new Promise((queryCreatedResolve) => {
|
||||||
p = new Promise((resolve) => {
|
p = new Promise((resolve) => {
|
||||||
getNonce.then((nonce) => {
|
getNonce.then((nonce) => {
|
||||||
queries[nonce] = { resolve };
|
queries[nonce] = { resolve };
|
||||||
|
@ -575,7 +575,7 @@
|
||||||
// kernelNonce. We won't be ready to receive the kernel nonce until after
|
// kernelNonce. We won't be ready to receive the kernel nonce until after
|
||||||
// the queries[nonce] object has been created.
|
// the queries[nonce] object has been created.
|
||||||
let readyForKernelNonce;
|
let readyForKernelNonce;
|
||||||
let getReadyForKernelNonce = new Promise((resolve) => {
|
const getReadyForKernelNonce = new Promise((resolve) => {
|
||||||
readyForKernelNonce = resolve;
|
readyForKernelNonce = resolve;
|
||||||
});
|
});
|
||||||
// Create the sendUpdate function. It defaults to doing nothing. After the
|
// 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]
|
// This promise cannot itself be created until the queries[nonce]
|
||||||
// object has been created, so block for the query to be created.
|
// 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) => {
|
haveQueryCreated.then((nonce) => {
|
||||||
queries[nonce]["kernelNonceReceived"] = resolve;
|
queries[nonce]["kernelNonceReceived"] = resolve;
|
||||||
readyForKernelNonce();
|
readyForKernelNonce();
|
||||||
|
@ -620,14 +620,14 @@
|
||||||
haveQueryCreated.then((nonce) => {
|
haveQueryCreated.then((nonce) => {
|
||||||
getReadyForKernelNonce.then(() => {
|
getReadyForKernelNonce.then(() => {
|
||||||
// There are two types of messages we can send depending on whether
|
// There are two types of messages we can send depending on whether
|
||||||
// we are talking to skt.us or the background script.
|
// we are talking to kernel.lumeweb.com or the background script.
|
||||||
let kernelMessage = {
|
const kernelMessage = {
|
||||||
method,
|
method,
|
||||||
nonce,
|
nonce,
|
||||||
data,
|
data,
|
||||||
sendKernelNonce: sendUpdates,
|
sendKernelNonce: sendUpdates,
|
||||||
};
|
};
|
||||||
let backgroundMessage = {
|
const backgroundMessage = {
|
||||||
method: "newKernelQuery",
|
method: "newKernelQuery",
|
||||||
nonce,
|
nonce,
|
||||||
data: kernelMessage,
|
data: kernelMessage,
|
||||||
|
@ -635,7 +635,7 @@
|
||||||
// The message structure needs to adjust based on whether we are
|
// The message structure needs to adjust based on whether we are
|
||||||
// talking directly to the kernel or whether we are talking to the
|
// talking directly to the kernel or whether we are talking to the
|
||||||
// background page.
|
// background page.
|
||||||
if (kernelOrigin === "https://skt.us") {
|
if (kernelOrigin === "https://kernel.lumeweb.com") {
|
||||||
kernelSource.postMessage(kernelMessage, kernelOrigin);
|
kernelSource.postMessage(kernelMessage, kernelOrigin);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -713,8 +713,8 @@
|
||||||
// prevents the portal from lying about the download.
|
// prevents the portal from lying about the download.
|
||||||
function download(skylink) {
|
function download(skylink) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let downloadModule = "AQCIaQ0P-r6FwPEDq3auCZiuH_jqrHfqRcY7TjZ136Z_Yw";
|
const downloadModule = "AQCIaQ0P-r6FwPEDq3auCZiuH_jqrHfqRcY7TjZ136Z_Yw";
|
||||||
let data = {
|
const data = {
|
||||||
skylink,
|
skylink,
|
||||||
};
|
};
|
||||||
callModule(downloadModule, "secureDownload", data).then(([result, err]) => {
|
callModule(downloadModule, "secureDownload", data).then(([result, err]) => {
|
||||||
|
@ -737,8 +737,8 @@
|
||||||
// less required.
|
// less required.
|
||||||
function registryRead(publicKey, dataKey) {
|
function registryRead(publicKey, dataKey) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q";
|
const registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q";
|
||||||
let data = {
|
const data = {
|
||||||
publicKey,
|
publicKey,
|
||||||
dataKey,
|
dataKey,
|
||||||
};
|
};
|
||||||
|
@ -765,8 +765,8 @@
|
||||||
// safe set of functions for writing to the registry such as getsetjson.
|
// safe set of functions for writing to the registry such as getsetjson.
|
||||||
function registryWrite(keypair, dataKey, entryData, revision) {
|
function registryWrite(keypair, dataKey, entryData, revision) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q";
|
const registryModule = "AQCovesg1AXUzKXLeRzQFILbjYMKr_rvNLsNhdq5GbYb2Q";
|
||||||
let callData = {
|
const callData = {
|
||||||
publicKey: keypair.publicKey,
|
publicKey: keypair.publicKey,
|
||||||
secretKey: keypair.secretKey,
|
secretKey: keypair.secretKey,
|
||||||
dataKey,
|
dataKey,
|
||||||
|
@ -791,8 +791,8 @@
|
||||||
function upload(filename, fileData) {
|
function upload(filename, fileData) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Prepare the module call.
|
// Prepare the module call.
|
||||||
let uploadModule = "AQAT_a0MzOInZoJzt1CwBM2U8oQ3GIfP5yKKJu8Un-SfNg";
|
const uploadModule = "AQAT_a0MzOInZoJzt1CwBM2U8oQ3GIfP5yKKJu8Un-SfNg";
|
||||||
let data = {
|
const data = {
|
||||||
filename,
|
filename,
|
||||||
fileData,
|
fileData,
|
||||||
};
|
};
|
||||||
|
@ -813,7 +813,7 @@
|
||||||
// distribution of the kernel".
|
// distribution of the kernel".
|
||||||
function kernelVersion() {
|
function kernelVersion() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let [, query] = newKernelQuery("version", {}, false);
|
const [, query] = newKernelQuery("version", {}, false);
|
||||||
query.then(([result, err]) => {
|
query.then(([result, err]) => {
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
resolve(["", "", err]);
|
resolve(["", "", err]);
|
||||||
|
@ -840,7 +840,8 @@
|
||||||
init: init,
|
init: init,
|
||||||
newKernelQuery: newKernelQuery,
|
newKernelQuery: newKernelQuery,
|
||||||
addContextToErr: addContextToErr$1,
|
addContextToErr: addContextToErr$1,
|
||||||
checkObj: checkObj
|
checkObj: checkObj,
|
||||||
|
objAsString: objAsString
|
||||||
});
|
});
|
||||||
|
|
||||||
// Blake2B, adapted from the reference implementation in RFC7693
|
// Blake2B, adapted from the reference implementation in RFC7693
|
||||||
|
@ -5219,5 +5220,15 @@
|
||||||
window.kernel = kernel;
|
window.kernel = kernel;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.skynet = skynet;
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
109
src/index.ts
109
src/index.ts
|
@ -6,12 +6,11 @@ import {
|
||||||
seedPhraseToSeed,
|
seedPhraseToSeed,
|
||||||
taggedRegistryEntryKeys,
|
taggedRegistryEntryKeys,
|
||||||
} from "libskynet";
|
} 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 * as path from "path";
|
||||||
import { overwriteRegistryEntry } from "libskynetnode";
|
import { overwriteRegistryEntry } from "libskynetnode";
|
||||||
import * as kernel from "libkernel";
|
import * as kernel from "libkernel";
|
||||||
import { webcrypto } from "crypto";
|
import { webcrypto } from "crypto";
|
||||||
|
import * as ed from "@noble/ed25519";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import StaticServer from "static-server";
|
import StaticServer from "static-server";
|
||||||
import { Page } from "puppeteer";
|
import { Page } from "puppeteer";
|
||||||
|
@ -19,118 +18,20 @@ import { errTuple } from "libskynet";
|
||||||
|
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.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() {
|
export function generateSeedPhrase() {
|
||||||
// Get the random numbers for the seed phrase. Typically, you need to
|
return ed.utils.randomPrivateKey();
|
||||||
// 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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(page: Page, seed = generateSeedPhrase()) {
|
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);
|
let seedHex = bufToHex(userSeed);
|
||||||
|
|
||||||
await page.evaluate((seed: string) => {
|
await page.evaluate((seed: string) => {
|
||||||
window.localStorage.setItem("v1-seed", seed);
|
window.localStorage.setItem("v1-key", seed);
|
||||||
}, seedHex);
|
}, 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) {
|
export async function loadTester(page: Page, port = 8080) {
|
||||||
|
|
|
@ -5,3 +5,14 @@ import * as skynet from "libskynet";
|
||||||
window.kernel = kernel;
|
window.kernel = kernel;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.skynet = skynet;
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue