*Update for new system design and use light client
This commit is contained in:
parent
4a224bd8d6
commit
575b3198fb
2
build.js
2
build.js
|
@ -3,7 +3,7 @@ import esbuild from "esbuild"
|
|||
esbuild.buildSync({
|
||||
entryPoints: ['src-module/index.ts'],
|
||||
outfile: 'dist-module/index.js',
|
||||
format: 'esm',
|
||||
format: 'iife',
|
||||
bundle: true,
|
||||
legalComments: 'external',
|
||||
// minify: true
|
||||
|
|
21
package.json
21
package.json
|
@ -10,16 +10,23 @@
|
|||
"build-module": "npm run compile-module && node ./dist-build/build.mjs dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lumeweb/kernel-libresolver": "https://github.com/LumeWeb/kernel-libresolver.git",
|
||||
"@lumeweb/libresolver": "https://github.com/LumeWeb/libresolver.git",
|
||||
"@lumeweb/tld-enum": "github:LumeWeb/list-of-top-level-domains"
|
||||
"@lumeweb/kernel-handshake-client": "git+https://git.lumeweb.com/LumeWeb/kernel-handshake-client.git",
|
||||
"@lumeweb/kernel-libresolver": "git+https://git.lumeweb.com/LumeWeb/kernel-libresolver.git",
|
||||
"@lumeweb/libresolver": "git+https://git.lumeweb.com/LumeWeb/libresolver.git",
|
||||
"@lumeweb/tld-enum": "git+https://git.lumeweb.com/LumeWeb/list-of-top-level-domains.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lumeweb/relay-types": "https://github.com/LumeWeb/relay-types.git",
|
||||
"@types/node": "^18.7.8",
|
||||
"@lumeweb/cfg": "git+https://git.lumeweb.com/LumeWeb/cfg.git",
|
||||
"@lumeweb/relay-types": "git+https://git.lumeweb.com/LumeWeb/relay-types.git",
|
||||
"@scure/bip39": "^1.1.1",
|
||||
"@skynetlabs/skynet-nodejs": "^2.9.0",
|
||||
"@types/node": "^18.14.0",
|
||||
"@types/read": "^0.0.29",
|
||||
"esbuild": "^0.15.5",
|
||||
"cli-progress": "^3.12.0",
|
||||
"esbuild": "^0.15.18",
|
||||
"libskynetnode": "^0.1.4",
|
||||
"read": "^1.0.7"
|
||||
"node-cache": "^5.1.2",
|
||||
"read": "^1.0.7",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
// This is the standard build script for a kernel module.
|
||||
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
addContextToErr,
|
||||
b64ToBuf,
|
||||
bufToHex,
|
||||
deriveRegistryEntryID,
|
||||
entryIDToSkylink,
|
||||
generateSeedPhraseDeterministic,
|
||||
seedPhraseToSeed,
|
||||
sha512,
|
||||
taggedRegistryEntryKeys,
|
||||
} from "libskynet";
|
||||
import {
|
||||
generateSeedPhraseRandom,
|
||||
overwriteRegistryEntry,
|
||||
upload,
|
||||
} from "libskynetnode";
|
||||
import read from "read";
|
||||
import * as bip39 from "@scure/bip39";
|
||||
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
||||
//@ts-ignore
|
||||
import { SkynetClient } from "@skynetlabs/skynet-nodejs";
|
||||
|
||||
// Helper variables to make it easier to return empty values alongside errors.
|
||||
const nu8 = new Uint8Array(0);
|
||||
|
@ -30,7 +18,7 @@ const nkp = {
|
|||
// caller.
|
||||
function readFile(fileName: string): [string, string | null] {
|
||||
try {
|
||||
const data = fs.readFileSync(fileName, "utf8");
|
||||
let data = fs.readFileSync(fileName, "utf8");
|
||||
return [data, null];
|
||||
} catch (err) {
|
||||
return ["", "unable to read file: " + JSON.stringify(err)];
|
||||
|
@ -41,7 +29,7 @@ function readFile(fileName: string): [string, string | null] {
|
|||
// for the caller.
|
||||
function readFileBinary(fileName: string): [Uint8Array, string | null] {
|
||||
try {
|
||||
const data = fs.readFileSync(fileName, null);
|
||||
let data = fs.readFileSync(fileName, null);
|
||||
return [data, null];
|
||||
} catch (err) {
|
||||
return [nu8, "unable to read file: " + JSON.stringify(err)];
|
||||
|
@ -59,60 +47,6 @@ function writeFile(fileName: string, fileData: string): string | null {
|
|||
}
|
||||
}
|
||||
|
||||
// hardenedSeedPhrase will take a password, harden it with 100,000 iterations
|
||||
// of hashing, and then turn it into a seed phrase.
|
||||
function hardenedSeedPhrase(password: string): [string, string | null] {
|
||||
// Add some hashing iterations to the password to make it stronger.
|
||||
for (let i = 0; i < 1000000; i++) {
|
||||
const passU8 = new TextEncoder().encode(password);
|
||||
const hashIter = sha512(passU8);
|
||||
password = bufToHex(hashIter);
|
||||
}
|
||||
return generateSeedPhraseDeterministic(password);
|
||||
}
|
||||
|
||||
// seedPhraseToRegistryKeys will convert a seed phrase to the set of registry
|
||||
// keys that govern the registry entry where the module is published.
|
||||
function seedPhraseToRegistryKeys(
|
||||
seedPhrase: string
|
||||
): [any, Uint8Array, string | null] {
|
||||
const [seed, errVSP] = seedPhraseToSeed(seedPhrase);
|
||||
if (errVSP !== null) {
|
||||
return [nkp, nu8, addContextToErr(errVSP, "unable to compute seed phrase")];
|
||||
}
|
||||
const [keypair, datakey, errTREK] = taggedRegistryEntryKeys(
|
||||
seed,
|
||||
"module-build",
|
||||
"module-key"
|
||||
);
|
||||
if (errTREK !== null) {
|
||||
return [
|
||||
nkp,
|
||||
nu8,
|
||||
addContextToErr(errTREK, "unable to compute registry entry keys"),
|
||||
];
|
||||
}
|
||||
return [keypair, datakey, null];
|
||||
}
|
||||
|
||||
// seedPhraseToRegistryLink will take a seedPhrase as input and convert it to
|
||||
// the registry link for the module.
|
||||
function seedPhraseToRegistryLink(seedPhrase: string): [string, string | null] {
|
||||
const [keypair, datakey, errSPTRK] = seedPhraseToRegistryKeys(seedPhrase);
|
||||
if (errSPTRK !== null) {
|
||||
return ["", addContextToErr(errSPTRK, "unable to compute registry keys")];
|
||||
}
|
||||
const [entryID, errDREID] = deriveRegistryEntryID(keypair.publicKey, datakey);
|
||||
if (errDREID !== null) {
|
||||
return [
|
||||
"",
|
||||
addContextToErr(errDREID, "unable to compute registry entry id"),
|
||||
];
|
||||
}
|
||||
const registryLink = entryIDToSkylink(entryID);
|
||||
return [registryLink, null];
|
||||
}
|
||||
|
||||
// handlePass handles all portions of the script that occur after the password
|
||||
// has been requested. If no password needs to be requested, handlePass will be
|
||||
// called with a null input. We need to structure the code this way because the
|
||||
|
@ -146,8 +80,7 @@ function handlePass(password: string) {
|
|||
console.error("passwords do not match");
|
||||
process.exit(1);
|
||||
}
|
||||
password = password + moduleSalt;
|
||||
handlePassConfirm(password);
|
||||
handlePassConfirm(moduleSalt, password);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
@ -155,8 +88,7 @@ function handlePass(password: string) {
|
|||
// there's no need to confirm the password but we do
|
||||
// need to pass the logic off to the handlePassConfirm
|
||||
// callback.
|
||||
password = password + moduleSalt;
|
||||
handlePassConfirm(password);
|
||||
handlePassConfirm(moduleSalt, password);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Unable to read seedFile:", err);
|
||||
|
@ -167,7 +99,7 @@ function handlePass(password: string) {
|
|||
// handlePassConfirm handles the full script after the confirmation password
|
||||
// has been provided. If not confirmation password is needed, this function
|
||||
// will be called anyway using the unconfirmed password as input.
|
||||
function handlePassConfirm(password: string) {
|
||||
function handlePassConfirm(seed: string, password: string) {
|
||||
// Create the seedFile if it does not exist. For dev we just save the
|
||||
// seed to disk outright, because this is a dev build and therefore not
|
||||
// security sensitive. Also the dev seed does not get pushed to the
|
||||
|
@ -180,35 +112,16 @@ function handlePassConfirm(password: string) {
|
|||
// devices.
|
||||
if (!fs.existsSync(seedFile) && process.argv[2] !== "prod") {
|
||||
// Generate the seed phrase and write it to the file.
|
||||
const [seedPhrase, errGSP] = generateSeedPhraseRandom();
|
||||
if (errGSP !== null) {
|
||||
console.error("Unable to generate seed phrase:", errGSP);
|
||||
process.exit(1);
|
||||
}
|
||||
const errWF = writeFile(seedFile, seedPhrase);
|
||||
let seedPhrase = bip39.generateMnemonic(wordlist);
|
||||
let errWF = writeFile(seedFile, seedPhrase);
|
||||
if (errWF !== null) {
|
||||
console.error("unable to write file:", errWF);
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (!fs.existsSync(seedFile) && process.argv[2] === "prod") {
|
||||
// Generate the seed phrase.
|
||||
const [seedPhrase, errGSP] = hardenedSeedPhrase(password);
|
||||
if (errGSP !== null) {
|
||||
console.error("Unable to generate seed phrase:", errGSP);
|
||||
process.exit(1);
|
||||
}
|
||||
const [registryLink, errSPTRL] = seedPhraseToRegistryLink(seedPhrase);
|
||||
if (errSPTRL !== null) {
|
||||
console.error("Unable to generate registry link:", errSPTRL);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let seedPhrase = bip39.generateMnemonic(wordlist);
|
||||
// Write the registry link to the file.
|
||||
const errWF = writeFile(seedFile, registryLink);
|
||||
if (errWF !== null) {
|
||||
console.error("unable to write registry link file:", errWF);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Load or verify the seed. If this is prod, the password is used to
|
||||
|
@ -218,85 +131,26 @@ function handlePassConfirm(password: string) {
|
|||
let registryLink: string;
|
||||
if (process.argv[2] === "prod") {
|
||||
// Generate the seed phrase from the password.
|
||||
const [sp, errGSP] = hardenedSeedPhrase(password);
|
||||
if (errGSP !== null) {
|
||||
console.error("Unable to generate seed phrase: ", errGSP);
|
||||
process.exit(1);
|
||||
}
|
||||
const [rl, errSPTRL] = seedPhraseToRegistryLink(sp);
|
||||
registryLink = rl;
|
||||
if (errSPTRL !== null) {
|
||||
console.error("Unable to generate registry link:", errSPTRL);
|
||||
process.exit(1);
|
||||
}
|
||||
const [registryLinkVerify, errRF] = readFile(seedFile);
|
||||
if (errRF !== null) {
|
||||
console.error("unable to read seedFile");
|
||||
process.exit(1);
|
||||
}
|
||||
const replacedRegistryLinkVerify = registryLinkVerify.replace(/\n$/, "");
|
||||
if (registryLink !== replacedRegistryLinkVerify) {
|
||||
console.error("Incorrect password");
|
||||
process.exit(1);
|
||||
}
|
||||
seedPhrase = sp;
|
||||
seedPhrase = bip39.generateMnemonic(wordlist);
|
||||
} else {
|
||||
const [sp, errRF] = readFile(seedFile);
|
||||
let [sp, errRF] = readFile(seedFile);
|
||||
if (errRF !== null) {
|
||||
console.error("unable to read seed phrase for dev command from disk");
|
||||
process.exit(1);
|
||||
}
|
||||
const [rl, errSPTRL] = seedPhraseToRegistryLink(sp);
|
||||
registryLink = rl;
|
||||
if (errSPTRL !== null) {
|
||||
console.error("Unable to generate registry link:", errSPTRL);
|
||||
process.exit(1);
|
||||
}
|
||||
// Write the registry link to the module skylink dev file.
|
||||
const errWF = writeFile("build/module-skylink-dev", registryLink);
|
||||
if (errWF !== null) {
|
||||
console.error("unable to write registry link file:", errWF);
|
||||
process.exit(1);
|
||||
}
|
||||
seedPhrase = sp;
|
||||
}
|
||||
|
||||
// Upload the module to Skynet.
|
||||
const [distFile, errRF] = readFileBinary("dist-module/index.js");
|
||||
if (errRF !== null) {
|
||||
console.error("unable to read dist file for module");
|
||||
process.exit(1);
|
||||
}
|
||||
const metadata = {
|
||||
let metadata = {
|
||||
Filename: "index.js",
|
||||
};
|
||||
console.log("Uploading module...");
|
||||
upload(distFile, metadata)
|
||||
.then((result) => {
|
||||
console.log("Updating module's registry entry...");
|
||||
// Update the v2 skylink.
|
||||
const [keypair, datakey, errSPTRK] = seedPhraseToRegistryKeys(seedPhrase);
|
||||
if (errSPTRK !== null) {
|
||||
return [
|
||||
"",
|
||||
addContextToErr(errSPTRK, "unable to compute registry keys"),
|
||||
];
|
||||
}
|
||||
const [bufLink, errBTB] = b64ToBuf(result);
|
||||
if (errBTB !== null) {
|
||||
return ["", addContextToErr(errBTB, "unable to decode skylink")];
|
||||
}
|
||||
overwriteRegistryEntry(keypair, datakey, bufLink)
|
||||
.then(() => {
|
||||
console.log("registry entry is updated");
|
||||
console.log("Immutable Link for Module:", result);
|
||||
console.log("Resolver Link for Module:", registryLink);
|
||||
const client = new SkynetClient("https://web3portal.com");
|
||||
client
|
||||
.uploadFile("dist-module/index.js")
|
||||
.then((result: any) => {
|
||||
console.log("Immutable Link for kernel:", result);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log("unable to update registry entry:", err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("unable to upload file", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
@ -331,25 +185,20 @@ if (process.argv[2] === "prod") {
|
|||
// not, create it.
|
||||
let moduleSalt: string;
|
||||
if (!fs.existsSync(".module-salt")) {
|
||||
const [ms, errGSPR] = generateSeedPhraseRandom();
|
||||
if (errGSPR !== null) {
|
||||
console.error("unable to generate module salt:", errGSPR);
|
||||
process.exit(1);
|
||||
}
|
||||
moduleSalt = ms;
|
||||
const errWF = writeFile(".module-salt", moduleSalt);
|
||||
moduleSalt = bip39.generateMnemonic(wordlist);
|
||||
let errWF = writeFile(".module-salt", moduleSalt);
|
||||
if (errWF !== null) {
|
||||
console.error("unable to write module salt file:", errWF);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
const [ms, errRF] = readFile(".module-salt");
|
||||
let [ms, errRF] = readFile(".module-salt");
|
||||
if (errRF !== null) {
|
||||
console.error("unable to read moduleSalt");
|
||||
process.exit(1);
|
||||
}
|
||||
const replaceMS = ms.replace(/\n$/, "");
|
||||
moduleSalt = replaceMS;
|
||||
ms = ms.replace(/\n$/, "");
|
||||
moduleSalt = ms;
|
||||
}
|
||||
|
||||
// Need to get a password if this is a prod build.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { setup } from "@lumeweb/kernel-libresolver";
|
||||
import Handshake from "../src/index.js";
|
||||
|
||||
setup(Handshake);
|
||||
setup(new Handshake());
|
||||
|
|
41
src/index.ts
41
src/index.ts
|
@ -15,7 +15,13 @@ import {
|
|||
getTld,
|
||||
resolverError,
|
||||
} from "@lumeweb/libresolver";
|
||||
import { RPCResponse } from "@lumeweb/relay-types";
|
||||
import {
|
||||
createClient,
|
||||
Response as HandshakeResponse,
|
||||
} from "@lumeweb/kernel-handshake-client";
|
||||
import { ResolverModule } from "@lumeweb/kernel-libresolver";
|
||||
|
||||
const client = createClient();
|
||||
|
||||
const HIP5_EXTENSIONS = ["eth", "_eth"];
|
||||
|
||||
|
@ -26,26 +32,20 @@ interface HnsRecord {
|
|||
ns: string;
|
||||
}
|
||||
|
||||
interface HandshakeRPCResponse extends RPCResponse {
|
||||
data?: {
|
||||
records: HnsRecord[];
|
||||
};
|
||||
}
|
||||
|
||||
export default class Handshake extends AbstractResolverModule {
|
||||
private async buildBlacklist(): Promise<Set<string>> {
|
||||
const blacklist = new Set<string>();
|
||||
let resolvers = this.resolver.resolvers;
|
||||
let resolvers = this.resolver.resolvers as unknown as Set<ResolverModule>;
|
||||
if (isPromise(resolvers as any)) {
|
||||
resolvers = await resolvers;
|
||||
}
|
||||
|
||||
for (const resolver of resolvers) {
|
||||
let tlds = resolver.getSupportedTlds();
|
||||
let tlds: string[] | Promise<string[]> = resolver.getSupportedTlds();
|
||||
if (isPromise(tlds as any)) {
|
||||
tlds = await tlds;
|
||||
}
|
||||
tlds.map((item) => blacklist.add(item));
|
||||
(tlds as string[]).map((item: string) => blacklist.add(item));
|
||||
}
|
||||
|
||||
return blacklist;
|
||||
|
@ -73,25 +73,25 @@ export default class Handshake extends AbstractResolverModule {
|
|||
return resolverEmptyResponse();
|
||||
}
|
||||
|
||||
const chainRecords = await this.query(tld, bypassCache);
|
||||
const chainRecords = await this.query(tld);
|
||||
if (chainRecords.error) {
|
||||
return resolverError(chainRecords.error);
|
||||
}
|
||||
|
||||
if (!chainRecords.data?.records.length) {
|
||||
if (!chainRecords.result?.records.length) {
|
||||
return resolverEmptyResponse();
|
||||
}
|
||||
|
||||
let records: DNSRecord[] = [];
|
||||
|
||||
for (const record of chainRecords.data?.records) {
|
||||
for (const record of chainRecords.result?.records) {
|
||||
switch (record.type) {
|
||||
case "NS": {
|
||||
await this.processNs(
|
||||
domain,
|
||||
record,
|
||||
records,
|
||||
chainRecords.data?.records,
|
||||
chainRecords.result?.records,
|
||||
options,
|
||||
bypassCache
|
||||
);
|
||||
|
@ -269,17 +269,8 @@ export default class Handshake extends AbstractResolverModule {
|
|||
}
|
||||
}
|
||||
|
||||
private async query(
|
||||
tld: string,
|
||||
bypassCache: boolean
|
||||
): Promise<HandshakeRPCResponse> {
|
||||
let query = this.resolver.rpcNetwork.wisdomQuery(
|
||||
"getnameresource",
|
||||
"handshake",
|
||||
[tld],
|
||||
bypassCache
|
||||
);
|
||||
return (await query.result) as HandshakeRPCResponse;
|
||||
private async query(tld: string): Promise<HandshakeResponse> {
|
||||
return client.query("getnameresource", [tld, true]);
|
||||
}
|
||||
|
||||
private async processTxt(
|
||||
|
|
|
@ -6,8 +6,14 @@
|
|||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist-build",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src-build"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
"include": [
|
||||
"src-build"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/__tests__/*"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue