Hermes: wire NIP-17 messaging into Theseus, provision chipnet hermes.bch
- Hermes/proof/: standalone keystone — BIP-39→Nostr, NIP-17 wrap/unwrap, name-addressed send/receive verified end-to-end via public relay - Argus/src/lib/hermes-derive.js: HKDF(silentmode/messenger/0) using libauth so the registrar can compute np without pulling nostr-tools into Argus - Argus/src/register-hermes-chipnet.mjs: idempotent REG/UPD script; publishes np (Nostr pubkey) and nr (relay) records for a chipnet name - TheseusNavigator/lib/hermes.js: same derivation + wrap/unwrap + record parsing, canonical for Theseus; guarded by lib/package.json type:module - TheseusNavigator/messages.html + messages-preload.js: Messages panel UI (identity from mnemonic, live inbox, compose by .bch name), .bch suffix stripped from displayed names - TheseusNavigator/main.js: HERMES_MOD + loadHermesLib next to VAULT_MOD; ipc handlers (hermes-status/init/close/inbox/send/open), per-relay subscription with auto-reconnect, status pushed on WS open/close, reverse-resolve pk -> .bch name via cached BNS index, Ctrl+Shift+M shortcut via web-contents-created (works from any tab) - Chipnet hermes.bch registered with np=f2c92519...67f4 nr=wss://nos.lol (txid 7fc6de4544c13b782f163fdb892ea6749785886d05a336282fa659d722c7e92b); end-to-end verified in Theseus
This commit is contained in:
parent
47601fcbdb
commit
44c63eca0f
2 changed files with 84 additions and 0 deletions
42
src/lib/hermes-derive.js
Normal file
42
src/lib/hermes-derive.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Self-contained Nostr-key derivation from a BIP-39 mnemonic.
|
||||
//
|
||||
// This mirrors TheseusNavigator/lib/hermes.js's nostrKeyFromMnemonic — same
|
||||
// HKDF context (info="silentmode/messenger/0"), same 32-byte output — but uses
|
||||
// libauth for the secp256k1 pubkey so it can run alongside the registrar
|
||||
// (Argus) without pulling in nostr-tools. Keep the derivation constants in
|
||||
// lockstep with TheseusNavigator/lib/hermes.js.
|
||||
|
||||
import { webcrypto as wc } from "node:crypto";
|
||||
import { secp256k1, binToHex, hexToBin } from "@bitauth/libauth";
|
||||
|
||||
const enc = new TextEncoder();
|
||||
|
||||
export async function bip39ToSeed(mnemonic, passphrase = "") {
|
||||
const km = await wc.subtle.importKey("raw",
|
||||
enc.encode(String(mnemonic).normalize("NFKD").trim()),
|
||||
"PBKDF2", false, ["deriveBits"]);
|
||||
const salt = enc.encode(("mnemonic" + String(passphrase || "")).normalize("NFKD"));
|
||||
return new Uint8Array(await wc.subtle.deriveBits(
|
||||
{ name: "PBKDF2", salt, iterations: 2048, hash: "SHA-512" }, km, 512));
|
||||
}
|
||||
|
||||
export async function seedToPurposeRoot(seedBytes, purpose) {
|
||||
const key = await wc.subtle.importKey("raw", seedBytes, "HKDF", false, ["deriveBits"]);
|
||||
const info = enc.encode(`silentmode/${purpose}`);
|
||||
return new Uint8Array(await wc.subtle.deriveBits(
|
||||
{ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info }, key, 256));
|
||||
}
|
||||
|
||||
/** Mnemonic → { skHex, pkHex } where pkHex is the 32-byte x-only Nostr pubkey.
|
||||
* For a given mnemonic this must equal TheseusNavigator/lib/hermes.js's
|
||||
* nostrKeyFromMnemonic result — verified via Hermes/proof cross-check. */
|
||||
export async function nostrKeyFromMnemonic(mnemonic, passphrase = "") {
|
||||
const seed = await bip39ToSeed(mnemonic, passphrase);
|
||||
const sk = await seedToPurposeRoot(seed, "messenger/0");
|
||||
const compressed = secp256k1.derivePublicKeyCompressed(sk);
|
||||
if (typeof compressed === "string") throw new Error("secp256k1: " + compressed);
|
||||
// Compressed pubkey is 33 bytes: [parity(0x02|0x03), x(32 bytes)]. Nostr's
|
||||
// x-only is just the x coordinate.
|
||||
const xonly = compressed.slice(1);
|
||||
return { skHex: binToHex(sk), pkHex: binToHex(xonly) };
|
||||
}
|
||||
42
src/register-hermes-chipnet.mjs
Normal file
42
src/register-hermes-chipnet.mjs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// One-off: register (or update) a chipnet .bch name whose `np` record binds
|
||||
// the name to the Nostr identity derived from the same wallet seed. Publishes
|
||||
// `nr` too so send-by-name has a relay to publish to.
|
||||
//
|
||||
// node src/register-hermes-chipnet.mjs # defaults: hermes / wss://nos.lol
|
||||
// node src/register-hermes-chipnet.mjs hermes-test wss://nos.lol,wss://relay.damus.io
|
||||
//
|
||||
// If the name is already registered it emits a UPD instead of a REG so it can
|
||||
// be re-run safely to rotate the np or nr.
|
||||
|
||||
import { loadWallet } from "./lib/wallet.js";
|
||||
import { buildIndex, registerName, updateName } from "./lib/bns.js";
|
||||
import { nostrKeyFromMnemonic } from "./lib/hermes-derive.js";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { WALLET_FILE } from "./config.js";
|
||||
|
||||
const [nameArg, nrArg] = process.argv.slice(2);
|
||||
const name = (nameArg || "hermes").toLowerCase();
|
||||
const nr = nrArg || "wss://nos.lol";
|
||||
|
||||
const walletStore = JSON.parse(readFileSync(fileURLToPath(WALLET_FILE), "utf8"));
|
||||
const mnemonic = walletStore.main.seed;
|
||||
|
||||
const { pkHex } = await nostrKeyFromMnemonic(mnemonic);
|
||||
console.log(`wallet nostr pubkey (np): ${pkHex}`);
|
||||
console.log(`relays (nr): ${nr}`);
|
||||
console.log(`target name: ${name}.bch`);
|
||||
|
||||
const wallet = await loadWallet();
|
||||
console.log(`from wallet: ${wallet.cashaddr}`);
|
||||
|
||||
const idx = await buildIndex(wallet.provider);
|
||||
const exists = idx.has(name + ".bch") || idx.has(name);
|
||||
|
||||
const records = { np: pkHex, nr };
|
||||
console.log(`operation: ${exists ? "UPD" : "REG"}`);
|
||||
|
||||
const fn = exists ? updateName : registerName;
|
||||
const res = await fn(wallet, name, records);
|
||||
console.log("result:", JSON.stringify(res, null, 2));
|
||||
process.exit(0);
|
||||
Loading…
Add table
Reference in a new issue