From e04377f029505c099f05703b10bd88a96b3bb78e Mon Sep 17 00:00:00 2001 From: TheRON Date: Sun, 14 Jun 2026 07:48:48 -0400 Subject: [PATCH] Updated --- hubzilla/addon/g1wallet/view/js/g1wallet.js | 1045 +------------------ 1 file changed, 10 insertions(+), 1035 deletions(-) diff --git a/hubzilla/addon/g1wallet/view/js/g1wallet.js b/hubzilla/addon/g1wallet/view/js/g1wallet.js index 40211b1..715c9fd 100644 --- a/hubzilla/addon/g1wallet/view/js/g1wallet.js +++ b/hubzilla/addon/g1wallet/view/js/g1wallet.js @@ -1,1046 +1,21 @@ /** - * g1wallet.js — Ğ1 Wallet client-side implementation. + * g1wallet.js — Ğ1 Wallet client-side. * - * DERIVATION (Substrate / Duniter v2 / Ğecko standard): - * 12-word BIP39 mnemonic - * → mnemonicToEntropy() → 16 bytes of raw entropy - * → use entropy directly as Ed25519 seed (the Substrate "mini-secret") - * → SubtleCrypto.importKey("raw", seed, "Ed25519", false, ["sign"]) - * → SubtleCrypto.sign("Ed25519", key, empty) → NOT used for sig; - * instead derive pubkey via sign(empty) trick or via key export + * Current scope: none. Address registration and balance display are + * handled server-side. No JS is required for the current feature set. * - * NOTE ON PUBKEY EXTRACTION: - * SubtleCrypto does not provide a direct "get public key from private seed" - * for Ed25519. The approach used here: - * - Import as CryptoKeyPair using generateKey with extractable=false is not - * applicable (generateKey doesn't accept a seed). - * - Instead: import the 32-byte seed as a raw "Ed25519" private key with - * extractable=false, then derive the public key via importKey with the - * PKCS8 DER wrapper (which wraps the seed), then use exportKey("spki") - * on the public key half to extract the 32-byte public key bytes. + * Future: transaction signing (sr25519/Schnorrkel via WASM) when + * scn01 scenario submission payment is wired. * - * Concretely: - * 1. Wrap seed in Ed25519 PKCS8 DER envelope (fixed 16-byte header + 32-byte seed) - * 2. importKey("pkcs8", der, "Ed25519", true, ["sign"]) → CryptoKeyPair (private) - * 3. Derive public key via importKey of the corresponding SPKI, OR: - * Use generateKey workaround — import via PKCS8, extract SPKI from the - * corresponding key using SubtleCrypto.exportKey approach. - * The cleanest path: after PKCS8 import, call SubtleCrypto.sign on a known - * message, then verify — but we need the pubkey bytes for SS58. - * - * ACTUAL APPROACH (cross-browser, verified pattern): - * - Wrap seed in PKCS8 DER (RFC 5958, Ed25519 OID 1.3.101.112) - * - importKey("pkcs8", der, {name:"Ed25519"}, true, ["sign"]) → private CryptoKey - * - The browser does NOT give us the public key from pkcs8 import alone. - * - Solution: also import the same seed as a raw "X25519" key... no, wrong curve. - * - CORRECT SOLUTION: use the SubtleCrypto generateKey approach with a - * known-seed workaround is not available in the Web Crypto spec. - * - FALLBACK: use a pure-JS Ed25519 implementation for pubkey derivation only. - * The private key for signing is still held as a non-extractable CryptoKey. - * - * IMPLEMENTED APPROACH (pragmatic, spec-safe): - * Phase 1 (this file): entropy → seed → use tweetnacl-style scalar mult - * to derive pubkey bytes in pure JS (32 bytes). The private key is then - * imported into SubtleCrypto as non-extractable for all signing operations. - * The pubkey bytes are SS58-encoded for display and storage. - * - * For pubkey derivation we use the standard Ed25519 base point scalar - * multiplication. Rather than bundle a full nacl library, we use the - * minimal required implementation: clamp + scalar_mult on the Ed25519 - * base point, which is ~80 lines of standard arithmetic. - * - * CRITICAL RULES (never relax): - * - The private key NEVER leaves the browser. - * - The mnemonic NEVER leaves the browser. - * - No key material in localStorage, sessionStorage, or cookies. - * - The private CryptoKey is non-extractable. - * - One document, one confirmation, one click — no batch signing. + * This file is intentionally minimal and kept as a placeholder so + * the load order and script tag are already in place. */ (function () { 'use strict'; - // ------------------------------------------------------------------------- - // SESSION STATE - // Held in module-scope memory only. Gone when the page is closed. - // ------------------------------------------------------------------------- - - var _session = { - unlocked: false, - pubkey: null, // base58-encoded SS58 Ğ1 address (g1...) - pubkeyHex: null, // 32-byte pubkey as hex, for signing reference - _cryptoKey: null // CryptoKey (non-extractable Ed25519 private key) - }; - - // ------------------------------------------------------------------------- - // ED25519 PUBKEY DERIVATION — minimal pure JS - // Standard Ed25519 scalar multiplication on the base point. - // Source: derived from the public domain TweetNaCl / SUPERCOP algorithms. - // Only the pubkey derivation path is implemented here — all signing goes - // through SubtleCrypto. - // ------------------------------------------------------------------------- - - var _ed25519 = (function () { - // Field arithmetic in GF(2^255 - 19) - // Each element is a 16-element array of 32-bit integers (signed) - - function gf(init) { - var r = new Float64Array(16); - if (init) for (var i = 0; i < init.length; i++) r[i] = init[i]; - return r; - } - - var _0 = new Uint8Array(16); - var _9 = new Uint8Array(32); _9[0] = 9; - - var gf0 = gf(), - gf1 = gf([1]), - D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, - 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), - D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, - 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]), - X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, - 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]), - Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, - 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]), - I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, - 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); - - function car25519(o) { - var c; - for (var i = 0; i < 16; i++) { - o[i] += 65536; - c = Math.floor(o[i] / 65536); - o[(i + 1) * (i < 15 ? 1 : 0)] += c - 1 + 37 * (c - 1) * (i === 15 ? 1 : 0); - o[i] -= c * 65536; - } - } - - function sel25519(p, q, b) { - var t, c = ~(b - 1); - for (var i = 0; i < 16; i++) { - t = c & (p[i] ^ q[i]); - p[i] ^= t; - q[i] ^= t; - } - } - - function pack25519(o, n) { - var i, j, b; - var 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 unpackneg(r, p) { - var 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(chk, den6, num); M(chk, chk, den); - pow2523(chk, chk); - M(chk, chk, num); M(chk, chk, den); M(chk, chk, den); M(r[0], chk, den); - S(t, r[0]); M(t, t, den); - if (!neq25519(t, num)) M(r[0], r[0], I); - S(t, r[0]); M(t, t, den); - if (!neq25519(t, 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 neq25519(a, b) { - var c = new Uint8Array(32), d = new Uint8Array(32); - pack25519(c, a); pack25519(d, b); - return cryptoNeq(c, 0, d, 0, 32); - } - - function cryptoNeq(x, xi, y, yi, n) { - var d = 0; - for (var i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; - return (1 & ((d - 1) >>> 8)) - 1; - } - - function par25519(a) { - var d = new Uint8Array(32); pack25519(d, a); return d[0] & 1; - } - - function unpack25519(o, n) { - for (var i = 0; i < 16; i++) o[i] = n[2 * i] + (n[2 * i + 1] << 8); - o[15] &= 0x7fff; - } - - function A(o, a, b) { for (var i = 0; i < 16; i++) o[i] = a[i] + b[i]; } - function Z(o, a, b) { for (var i = 0; i < 16; i++) o[i] = a[i] - b[i]; } - - function M(o, a, b) { - var t = new Float64Array(31); - for (var i = 0; i < 16; i++) - for (var j = 0; j < 16; j++) t[i + j] += a[i] * b[j]; - for (var i = 0; i < 15; i++) t[i] += 38 * t[i + 16]; - for (var i = 0; i < 16; i++) o[i] = t[i]; - car25519(o); car25519(o); - } - - function S(o, a) { M(o, a, a); } - - function pow2523(o, i) { - var c = gf(); - for (var a = 0; a < 16; a++) c[a] = i[a]; - for (var a = 250; a >= 0; a--) { - S(c, c); - if (a !== 1) M(c, c, i); - } - for (var a = 0; a < 16; a++) o[a] = c[a]; - } - - function set25519(r, a) { for (var i = 0; i < 16; i++) r[i] = a[i] | 0; } - - // 64-byte hash (SHA-512) — we delegate to SubtleCrypto for this. - // But we need it synchronously for key derivation. We use a - // synchronous SHA-512 based on @noble/hashes which is bundled in bip39. - // Actually — the bip39 lib does NOT export sha512 externally. - // We implement scalar mult with the clamped seed directly (Ed25519 spec): - // The "mini-secret" (32 bytes) is hashed with SHA-512 to get the - // 64-byte scalar, then the lower 32 bytes are clamped. - // We need SHA-512 synchronously. We implement a minimal SHA-512 here. - - // --- Minimal SHA-512 --- - // Based on FIPS 180-4, pure JS, no dependencies. - var K512 = [ - [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 sha512(msg) { - // msg: Uint8Array; returns Uint8Array(64) - var H = [ - [0x6a09e667, 0xf3bcc908], [0xbb67ae85, 0x84caa73b], - [0x3c6ef372, 0xfe94f82b], [0xa54ff53a, 0x5f1d36f1], - [0x510e527f, 0xade682d1], [0x9b05688c, 0x2b3e6c1f], - [0x1f83d9ab, 0xfb41bd6b], [0x5be0cd19, 0x137e2179] - ]; - - var len = msg.length; - var bitlen = len * 8; - // Padding - var padded = []; - for (var i = 0; i < len; i++) padded.push(msg[i]); - padded.push(0x80); - while ((padded.length % 128) !== 112) padded.push(0); - // 16 bytes of bit length (big-endian 128-bit), we only support < 2^32 bits - padded.push(0, 0, 0, 0, 0, 0, 0, 0); - padded.push((bitlen / 0x1000000 / 0x1000000) & 0xff); - padded.push((bitlen / 0x1000000 / 0x10000) & 0xff); - padded.push((bitlen / 0x1000000 / 0x100) & 0xff); - padded.push((bitlen / 0x1000000) & 0xff); - padded.push((bitlen / 0x1000000) >>> 0 & 0xff); // ensure no NaN - // fix: proper 8-byte big-endian - // re-do last 8 bytes properly - padded = padded.slice(0, padded.length - 5); - var bl = bitlen; - for (var i = 3; i >= 0; i--) { padded.push(bl & 0xff); bl = Math.floor(bl / 256); } - // Actually redo this properly: - var data = new Uint8Array(len + 1 + ((112 - (len + 1) % 128 + 128) % 128) + 16); - for (var i = 0; i < len; i++) data[i] = msg[i]; - data[len] = 0x80; - var blen = len * 8; - // write 128-bit big-endian bit length at end (we only use lower 64 bits) - data[data.length - 4] = (blen >>> 24) & 0xff; - data[data.length - 3] = (blen >>> 16) & 0xff; - data[data.length - 2] = (blen >>> 8) & 0xff; - data[data.length - 1] = blen & 0xff; - - function rotr64h(ah, al, r) { return r < 32 ? (ah >>> r) | (al << (32 - r)) : (al >>> (r - 32)) | (ah << (64 - r)); } - function rotr64l(ah, al, r) { return r < 32 ? (al >>> r) | (ah << (32 - r)) : (ah >>> (r - 32)) | (al << (64 - r)); } - - var W = new Array(160); // 80 pairs of [h, l] - for (var bi = 0; bi < data.length; bi += 128) { - for (var i = 0; i < 16; i++) { - W[2*i] = (data[bi + 8*i]<<24) | (data[bi+8*i+1]<<16) | (data[bi+8*i+2]<<8) | data[bi+8*i+3]; - W[2*i+1] = (data[bi+8*i+4]<<24) | (data[bi+8*i+5]<<16) | (data[bi+8*i+6]<<8) | data[bi+8*i+7]; - } - for (var i = 16; i < 80; i++) { - var g0h = rotr64h(W[2*(i-15)], W[2*(i-15)+1], 1) ^ rotr64h(W[2*(i-15)], W[2*(i-15)+1], 8) ^ (W[2*(i-15)] >>> 7); - var g0l = rotr64l(W[2*(i-15)], W[2*(i-15)+1], 1) ^ rotr64l(W[2*(i-15)], W[2*(i-15)+1], 8) ^ ((W[2*(i-15)] << 25) | (W[2*(i-15)+1] >>> 7)); - var g1h = rotr64h(W[2*(i-2)], W[2*(i-2)+1], 19) ^ rotr64h(W[2*(i-2)], W[2*(i-2)+1], 61) ^ (W[2*(i-2)] >>> 6); - var g1l = rotr64l(W[2*(i-2)], W[2*(i-2)+1], 19) ^ rotr64l(W[2*(i-2)], W[2*(i-2)+1], 61) ^ ((W[2*(i-2)] << 26) | (W[2*(i-2)+1] >>> 6)); - var xl = (W[2*(i-7)+1] >>> 0) + (g0l >>> 0) + (W[2*(i-16)+1] >>> 0) + (g1l >>> 0); - W[2*i+1] = xl >>> 0; - W[2*i] = (W[2*(i-7)] + g0h + W[2*(i-16)] + g1h + Math.floor(((W[2*(i-7)+1] >>> 0) + (g0l >>> 0) + (W[2*(i-16)+1] >>> 0) + (g1l >>> 0)) / 0x100000000)) >>> 0; - } - var ah=H[0][0],al=H[0][1],bh=H[1][0],bl2=H[1][1],ch=H[2][0],cl=H[2][1], - dh=H[3][0],dl=H[3][1],eh=H[4][0],el=H[4][1],fh=H[5][0],fl=H[5][1], - gh=H[6][0],gl=H[6][1],hh=H[7][0],hl=H[7][1]; - for (var i = 0; i < 80; i++) { - var s1h = rotr64h(eh,el,14) ^ rotr64h(eh,el,18) ^ rotr64h(eh,el,41); - var s1l = rotr64l(eh,el,14) ^ rotr64l(eh,el,18) ^ rotr64l(eh,el,41); - var chh = (eh & fh) ^ (~eh & gh); var chl = (el & fl) ^ (~el & gl); - var t1l = (hl>>>0)+(s1l>>>0)+(chl>>>0)+(K512[i][1]>>>0)+(W[2*i+1]>>>0); - var t1h = (hh+s1h+chh+K512[i][0]+W[2*i] + Math.floor(t1l/0x100000000))>>>0; - t1l = t1l>>>0; - var s0h = rotr64h(ah,al,28) ^ rotr64h(ah,al,34) ^ rotr64h(ah,al,39); - var s0l = rotr64l(ah,al,28) ^ rotr64l(ah,al,34) ^ rotr64l(ah,al,39); - var majh = (ah&bh)^(ah&ch)^(bh&ch); var majl = (al&bl2)^(al&cl)^(bl2&cl); - var t2l = (s0l>>>0)+(majl>>>0); var t2h = (s0h+majh+Math.floor(((s0l>>>0)+(majl>>>0))/0x100000000))>>>0; - hh=gh; hl=gl; gh=fh; gl=fl; fh=eh; fl=el; - var newel = (dl>>>0)+(t1l>>>0); eh=(dh+t1h+Math.floor(((dl>>>0)+(t1l>>>0))/0x100000000))>>>0; el=newel>>>0; - dh=ch; dl=cl; ch=bh; cl=bl2; bh=ah; bl2=al; - var newAl=(t1l>>>0)+(t2l>>>0); ah=(t1h+t2h+Math.floor(((t1l>>>0)+(t2l>>>0))/0x100000000))>>>0; al=newAl>>>0; - } - function addH(H, i, h, l) { - var nl = (H[i][1]>>>0)+(l>>>0); H[i][1]=(nl>>>0); H[i][0]=(H[i][0]+h+Math.floor(nl/0x100000000))>>>0; - } - addH(H,0,ah,al); addH(H,1,bh,bl2); addH(H,2,ch,cl); addH(H,3,dh,dl); - addH(H,4,eh,el); addH(H,5,fh,fl); addH(H,6,gh,gl); addH(H,7,hh,hl); - } - var out = new Uint8Array(64); - for (var i = 0; i < 8; i++) { - out[8*i] = (H[i][0] >>> 24) & 0xff; out[8*i+1] = (H[i][0] >>> 16) & 0xff; - out[8*i+2] = (H[i][0] >>> 8) & 0xff; out[8*i+3] = H[i][0] & 0xff; - out[8*i+4] = (H[i][1] >>> 24) & 0xff; out[8*i+5] = (H[i][1] >>> 16) & 0xff; - out[8*i+6] = (H[i][1] >>> 8) & 0xff; out[8*i+7] = H[i][1] & 0xff; - } - return out; - } - - // --- Ed25519 scalar multiplication on base point --- - // Derives 32-byte compressed public key from 32-byte seed. - // Per Ed25519 spec (RFC 8032): - // 1. h = SHA-512(seed) → 64 bytes - // 2. scalar s = h[0..31] with clamping - // 3. A = s * B (base point scalar mult) - // 4. Compress A → 32 bytes - - function scalarMult(s) { - // s: 32-byte Uint8Array (clamped scalar) - // returns 4 x gf elements: [x, y, z, t] - var p = [gf(), gf(), gf(), gf()], - q = [gf(), gf(), gf(), gf()]; - set25519(q[0], X); - set25519(q[1], Y); - set25519(q[2], gf1); - M(q[3], X, Y); - for (var i = 255; i >= 0; --i) { - var b = (s[(i / 8) | 0] >> (i & 7)) & 1; - add(p, q); - add(q, p); - add(p, q); - sel25519(p[0], q[0], b); - sel25519(p[1], q[1], b); - sel25519(p[2], q[2], b); - sel25519(p[3], q[3], b); - add(q, p); - add(p, q); - sel25519(p[0], q[0], b); - sel25519(p[1], q[1], b); - sel25519(p[2], q[2], b); - sel25519(p[3], q[3], b); - } - // Actually implement the twisted Edwards addition formula - // This is the standard nacl scalarmult approach - return p; - } - - function add(p, q) { - var 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 encodePoint(p, out) { - var tx = gf(), ty = gf(), zi = gf(); - inv25519(zi, p[2]); - M(tx, p[0], zi); M(ty, p[1], zi); - pack25519(out, ty); - out[31] ^= par25519(tx) << 7; - } - - function inv25519(o, i) { - var c = gf(); - for (var a = 0; a < 16; a++) c[a] = i[a]; - for (var a = 253; a >= 0; a--) { - S(c, c); - if (a !== 2 && a !== 4) M(c, c, i); - } - for (var a = 0; a < 16; a++) o[a] = c[a]; - } - - function derivePublicKey(seed32) { - // seed32: Uint8Array(32) — the mini-secret / raw entropy - var h = sha512(seed32); - // Clamp per Ed25519 RFC 8032 - h[0] &= 248; - h[31] &= 127; - h[31] |= 64; - // scalar = h[0..31] - var scalar = h.slice(0, 32); - // Base point - var p = [gf(), gf(), gf(), gf()]; - set25519(p[0], X); - set25519(p[1], Y); - set25519(p[2], gf1); - M(p[3], X, Y); - // Scalar multiplication - var q = [gf(), gf(), gf(), gf()]; - // Initialize q to identity - set25519(q[0], gf0); set25519(q[1], gf1); set25519(q[2], gf1); set25519(q[3], gf0); - for (var i = 255; i >= 0; --i) { - var b = (scalar[(i / 8) | 0] >> (i & 7)) & 1; - _cswap(p, q, b); - add(q, p); - add(p, p); - _cswap(p, q, b); - } - var pk = new Uint8Array(32); - encodePoint(q, pk); - return pk; - } - - function _cswap(p, q, b) { - for (var i = 0; i < 4; i++) sel25519(p[i], q[i], b); - } - - return { derivePublicKey: derivePublicKey }; - })(); - - // ------------------------------------------------------------------------- - // SS58 ENCODING — Ğ1 network prefix - // Ğ1 uses 2-byte prefix 0x5891 (14-bit extended SS58, network ID 4129) - // Format: prefix_bytes(2) + pubkey(32) + checksum(2) = 36 bytes total - // Checksum = first 2 bytes of Blake2b-512("SS58PRE" + prefix_bytes + pubkey) - // We delegate Blake2b to a synchronous implementation. - // Base58 alphabet: Bitcoin alphabet (same as SS58). - // ------------------------------------------------------------------------- - - var _BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - - function _base58Encode(bytes) { - // Count leading zeros - var zeros = 0; - for (var i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++; - // Convert to big integer - var digits = [0]; - for (var i = 0; i < bytes.length; i++) { - var carry = bytes[i]; - for (var j = 0; j < digits.length; j++) { - carry += digits[j] << 8; - digits[j] = carry % 58; - carry = Math.floor(carry / 58); - } - while (carry > 0) { digits.push(carry % 58); carry = Math.floor(carry / 58); } - } - var str = ''; - for (var i = 0; i < zeros; i++) str += '1'; - for (var i = digits.length - 1; i >= 0; i--) str += _BASE58_ALPHABET[digits[i]]; - return str; - } - - // Blake2b-512 for SS58 checksum. - // We need a synchronous Blake2b-512. The vendored Blake2b.php is server-side. - // For the client side we implement a minimal Blake2b-512 in JS. - // This is only used for the checksum (not for key derivation) so - // correctness is verifiable against known SS58 test vectors. - - var _blake2b = (function () { - // Blake2b implementation — minimal, synchronous, pure JS - // Based on the reference implementation (CC0 / public domain) - var BLAKE2B_IV32 = new Uint32Array([ - 0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85, - 0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A, - 0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C, - 0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19 - ]); - - var 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 - ]; - - var v = new Uint32Array(32), m = new Uint32Array(32); - - function B2B_GET32(arr, i) { - return (arr[i] ^ (arr[i + 1] << 8) ^ (arr[i + 2] << 16) ^ (arr[i + 3] << 24)) >>> 0; - } - - function B2B_G(a, b, c, d, ix, iy) { - var x0 = m[ix * 2], x1 = m[ix * 2 + 1], y0 = m[iy * 2], y1 = m[iy * 2 + 1]; - var va0 = v[a * 2], va1 = v[a * 2 + 1]; - var vb0 = v[b * 2], vb1 = v[b * 2 + 1]; - var vc0 = v[c * 2], vc1 = v[c * 2 + 1]; - var vd0 = v[d * 2], vd1 = v[d * 2 + 1]; - var w0, w1, xor0, xor1, r0, r1; - - // a = a + b + x - w0 = (va0 + vb0 + x0) >>> 0; w1 = (va1 + vb1 + x1 + Math.floor(((va0>>>0)+(vb0>>>0)+(x0>>>0))/0x100000000)) >>> 0; - va0 = w0; va1 = w1; - // d = (d XOR a) rotateright 32 - xor0 = vd0 ^ va0; xor1 = vd1 ^ va1; - vd0 = xor1; vd1 = xor0; - // c = c + d - w0 = (vc0 + vd0) >>> 0; w1 = (vc1 + vd1 + Math.floor(((vc0>>>0)+(vd0>>>0))/0x100000000)) >>> 0; - vc0 = w0; vc1 = w1; - // b = (b XOR c) rotateright 24 - xor0 = vb0 ^ vc0; xor1 = vb1 ^ vc1; - vb0 = (xor0 >>> 24) | (xor1 << 8); vb1 = (xor1 >>> 24) | (xor0 << 8); - vb0 >>>= 0; vb1 >>>= 0; - // a = a + b + y - w0 = (va0 + vb0 + y0) >>> 0; w1 = (va1 + vb1 + y1 + Math.floor(((va0>>>0)+(vb0>>>0)+(y0>>>0))/0x100000000)) >>> 0; - va0 = w0; va1 = w1; - // d = (d XOR a) rotateright 16 - xor0 = vd0 ^ va0; xor1 = vd1 ^ va1; - vd0 = (xor0 >>> 16) | (xor1 << 16); vd1 = (xor1 >>> 16) | (xor0 << 16); - vd0 >>>= 0; vd1 >>>= 0; - // c = c + d - w0 = (vc0 + vd0) >>> 0; w1 = (vc1 + vd1 + Math.floor(((vc0>>>0)+(vd0>>>0))/0x100000000)) >>> 0; - vc0 = w0; vc1 = w1; - // b = (b XOR c) rotateright 63 - xor0 = vb0 ^ vc0; xor1 = vb1 ^ vc1; - vb0 = (xor1 >>> 31) | (xor0 << 1); vb1 = (xor0 >>> 31) | (xor1 << 1); - vb0 >>>= 0; vb1 >>>= 0; - - v[a * 2] = va0; v[a * 2 + 1] = va1; - v[b * 2] = vb0; v[b * 2 + 1] = vb1; - v[c * 2] = vc0; v[c * 2 + 1] = vc1; - v[d * 2] = vd0; v[d * 2 + 1] = vd1; - } - - function blake2b(input, outlen) { - // input: Uint8Array, outlen: number of bytes (e.g. 64 for Blake2b-512) - outlen = outlen || 64; - var h = new Uint32Array(16), cbLo = 0, cbHi = 0; - // Initialize state - for (var i = 0; i < 16; i++) h[i] = BLAKE2B_IV32[i]; - h[0] ^= 0x01010000 ^ outlen; - - var out = new Uint8Array(outlen); - var buf = new Uint8Array(128); - var buflen = 0; - var inoff = 0; - var last = false; - - function compress(last) { - for (var i = 0; i < 16; i++) v[i] = h[i]; - v[16] = BLAKE2B_IV32[0]; v[17] = BLAKE2B_IV32[1]; - v[18] = BLAKE2B_IV32[2]; v[19] = BLAKE2B_IV32[3]; - v[20] = BLAKE2B_IV32[4]; v[21] = BLAKE2B_IV32[5]; - v[22] = BLAKE2B_IV32[6]; v[23] = BLAKE2B_IV32[7]; - v[24] = BLAKE2B_IV32[8]; v[25] = BLAKE2B_IV32[9]; - v[26] = BLAKE2B_IV32[10]; v[27] = BLAKE2B_IV32[11]; - v[28] = BLAKE2B_IV32[12]; v[29] = BLAKE2B_IV32[13]; - v[30] = BLAKE2B_IV32[14]; v[31] = BLAKE2B_IV32[15]; - v[24] ^= cbLo; v[25] ^= cbHi; - if (last) { v[28] = ~v[28]; v[29] = ~v[29]; } - - for (var i = 0; i < 32; i++) m[i] = B2B_GET32(buf, i * 4); - - for (var r = 0; r < 12; r++) { - B2B_G(0, 4, 8, 12, SIGMA8[r * 16 + 0], SIGMA8[r * 16 + 1]); - B2B_G(1, 5, 9, 13, SIGMA8[r * 16 + 2], SIGMA8[r * 16 + 3]); - B2B_G(2, 6, 10, 14, SIGMA8[r * 16 + 4], SIGMA8[r * 16 + 5]); - B2B_G(3, 7, 11, 15, SIGMA8[r * 16 + 6], SIGMA8[r * 16 + 7]); - B2B_G(0, 5, 10, 15, SIGMA8[r * 16 + 8], SIGMA8[r * 16 + 9]); - B2B_G(1, 6, 11, 12, SIGMA8[r * 16 + 10], SIGMA8[r * 16 + 11]); - B2B_G(2, 7, 8, 13, SIGMA8[r * 16 + 12], SIGMA8[r * 16 + 13]); - B2B_G(3, 4, 9, 14, SIGMA8[r * 16 + 14], SIGMA8[r * 16 + 15]); - } - for (var i = 0; i < 16; i++) h[i] ^= v[i] ^ v[i + 16]; - } - - function update(inp) { - for (var i = 0; i < inp.length; i++) { - if (buflen === 128) { - cbLo = (cbLo + 128) >>> 0; - if (cbLo < 128) cbHi++; - compress(false); - buflen = 0; - } - buf[buflen++] = inp[i]; - } - } - - update(input); - - // Final block - cbLo = (cbLo + buflen) >>> 0; - if (cbLo < buflen) cbHi++; - while (buflen < 128) buf[buflen++] = 0; - compress(true); - - for (var i = 0; i < outlen; i++) { - out[i] = h[Math.floor(i / 4) * 2 + (i % 4 < 2 ? (i % 4 === 0 ? 0 : 1) : (i % 4 === 2 ? 0 : 1))]; - // fix: read bytes correctly - } - // Correct byte extraction - for (var i = 0; i < outlen; i++) { - var hi = Math.floor(i / 4); - var lo = i % 4; - out[i] = (h[hi * 2 + (lo < 2 ? 0 : 0)] >> (8 * (lo % 4))) & 0xff; - } - // Actually: h is Uint32Array in little-endian pairs - // Each 8 bytes of output = two 32-bit words (lo word first, then hi word) - // lo word at h[i*2], hi word at h[i*2+1] - for (var i = 0; i < outlen; i++) { - var word_idx = Math.floor(i / 4); - var byte_pos = i % 4; - // h[word_idx] stores the word; it's little-endian so byte 0 is LSB - out[i] = (h[word_idx] >>> (8 * byte_pos)) & 0xff; - } - return out; - } - - return { hash: blake2b }; - })(); - - function _ss58Encode(pubkeyBytes) { - // pubkeyBytes: Uint8Array(32) - // Ğ1 network prefix: 2 bytes, little-endian encoding of 14-bit prefix - // prefix value 4129 (0x1021 in the 14-bit space) - // SS58 2-byte prefix encoding: split 14 bits as: - // byte0 = ((prefix & 0x3f) << 2) | 0x40 - // byte1 = (prefix >> 6) & 0xff - // For Ğ1: prefix = 36 (single-byte SS58 format... wait) - // Let's verify: Ğ1 addresses start with "g1" in base58. - // From DUNITER-RPC-FINDINGS: 2-byte prefix 0x5891 - // 0x5891 = bytes [0x58, 0x91] in decoded order (verified against live address) - var prefixBytes = new Uint8Array([0x58, 0x91]); - - // Payload = prefixBytes + pubkeyBytes (34 bytes total) - var payload = new Uint8Array(34); - payload.set(prefixBytes, 0); - payload.set(pubkeyBytes, 2); - - // Checksum input = "SS58PRE" + payload - var ss58pre = new Uint8Array([0x53, 0x53, 0x35, 0x38, 0x50, 0x52, 0x45]); // "SS58PRE" - var checksumInput = new Uint8Array(ss58pre.length + payload.length); - checksumInput.set(ss58pre, 0); - checksumInput.set(payload, ss58pre.length); - - var hash = _blake2b.hash(checksumInput, 64); - var checksum = hash.slice(0, 2); - - // Full = prefixBytes + pubkeyBytes + checksum = 36 bytes - var full = new Uint8Array(36); - full.set(prefixBytes, 0); - full.set(pubkeyBytes, 2); - full.set(checksum, 34); - - return _base58Encode(full); - } - - function _hexToBytes(hex) { - var bytes = new Uint8Array(hex.length / 2); - for (var i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); - return bytes; - } - - function _bytesToHex(bytes) { - var hex = ''; - for (var i = 0; i < bytes.length; i++) hex += ('0' + bytes[i].toString(16)).slice(-2); - return hex; - } - - // ------------------------------------------------------------------------- - // KEY DERIVATION PIPELINE - // mnemonic → entropy (hex) → seed (Uint8Array 32) → pubkey (Uint8Array 32) - // → SS58 address string - // → SubtleCrypto CryptoKey (non-extractable, for signing) - // ------------------------------------------------------------------------- - - function _deriveKeypair(mnemonic) { - // Returns a Promise resolving to { pubkeyAddress, pubkeyHex, cryptoKey } - var entropyHex = window.bip39.mnemonicToEntropy(mnemonic); - var seed = _hexToBytes(entropyHex); // 16 bytes for 12-word mnemonic - - // Pad seed to 32 bytes (Substrate uses 32-byte mini-secret; - // for 12-word BIP39, entropy is 16 bytes — pad with zeros to 32) - // Per Substrate source (sr_core/src/hex_serde.rs and schnorrkel): - // the 16-byte entropy IS used as-is as the 32-byte key if < 32 bytes, - // with zero-padding on the RIGHT. - var seed32 = new Uint8Array(32); - seed32.set(seed, 0); // left-aligned, zero-padded right - - // Derive Ed25519 public key - var pubkeyBytes; - try { - pubkeyBytes = _ed25519.derivePublicKey(seed32); - } catch (e) { - return Promise.reject(new Error('Key derivation failed: ' + e.message)); - } - - var pubkeyHex = _bytesToHex(pubkeyBytes); - var pubkeyAddress = _ss58Encode(pubkeyBytes); - - // Import the seed as a non-extractable Ed25519 signing key via PKCS8 - // PKCS8 DER wrapper for Ed25519 (RFC 5958): - // 30 2e SEQUENCE (46 bytes) - // 02 01 00 INTEGER 0 (version) - // 30 05 SEQUENCE (5 bytes) - // 06 03 2b 65 70 OID 1.3.101.112 (Ed25519) - // 04 22 OCTET STRING (34 bytes) - // 04 20 OCTET STRING (32 bytes) — the key - // [32 bytes of seed] - var pkcs8 = new Uint8Array([ - 0x30, 0x2e, - 0x02, 0x01, 0x00, - 0x30, 0x05, - 0x06, 0x03, 0x2b, 0x65, 0x70, - 0x04, 0x22, - 0x04, 0x20 - ].concat(Array.from(seed32))); - - return crypto.subtle.importKey( - 'pkcs8', - pkcs8.buffer, - { name: 'Ed25519' }, - false, // non-extractable - ['sign'] - ).then(function (cryptoKey) { - return { - pubkeyAddress: pubkeyAddress, - pubkeyHex: pubkeyHex, - cryptoKey: cryptoKey - }; - }); - } - - // ------------------------------------------------------------------------- - // PUBLIC API - // Exposed on window.g1wallet for other addons to use. - // ------------------------------------------------------------------------- - - window.g1wallet = { - - /** - * Returns true if the wallet is currently unlocked. - */ - isUnlocked: function () { - return _session.unlocked; - }, - - /** - * Returns the current session SS58 address (g1...), or null if locked. - */ - getPubkey: function () { - return _session.unlocked ? _session.pubkey : null; - }, - - /** - * Returns the current session pubkey hex (32 bytes), or null if locked. - */ - getPubkeyHex: function () { - return _session.unlocked ? _session.pubkeyHex : null; - }, - - /** - * Requests a signature from the wallet for a given document. - * If the wallet is locked, dispatches 'g1wallet:sign_request_blocked'. - * If unlocked, signs via SubtleCrypto and dispatches the callback event. - * - * @param {string|Uint8Array} document - The bytes to sign - * @param {string} callback - Event name to dispatch with result - */ - requestSignature: function (document, callback) { - if (!_session.unlocked || !_session._cryptoKey) { - window.dispatchEvent(new CustomEvent('g1wallet:sign_request_blocked', { - detail: { reason: 'Wallet is locked. Please unlock your wallet first.' } - })); - return; - } - var data = (typeof document === 'string') - ? new TextEncoder().encode(document) - : document; - - crypto.subtle.sign('Ed25519', _session._cryptoKey, data) - .then(function (sigBuf) { - var sigBytes = new Uint8Array(sigBuf); - var sigHex = _bytesToHex(sigBytes); - window.dispatchEvent(new CustomEvent(callback, { - detail: { - signature: sigHex, - pubkey: _session.pubkey, - pubkeyHex: _session.pubkeyHex - } - })); - }) - .catch(function (err) { - window.dispatchEvent(new CustomEvent(callback, { - detail: { error: err.message } - })); - }); - } - }; - - // ------------------------------------------------------------------------- - // WIDGET API - // Used by Widget/G1wallet.php inline script to register widget instances. - // ------------------------------------------------------------------------- - - window.G1WalletWidget = { - init: function (uid, walletUrl) { - window.addEventListener('g1wallet:unlocked', function (e) { - var statusEl = document.getElementById(uid + '-status'); - if (!statusEl) return; - var pubkey = e.detail.pubkey || ''; - var short = pubkey.substring(0, 12) + '…'; - statusEl.innerHTML = - '
' + - '🔓 ' + - 'Unlocked' + - '
' + - _escHtml(short) + - '
'; - }); - - window.addEventListener('g1wallet:locked', function () { - var statusEl = document.getElementById(uid + '-status'); - if (!statusEl) return; - statusEl.innerHTML = - '
' + - '🔒 ' + - 'Locked' + - '' + - '
'; - }); - } - }; - - // ------------------------------------------------------------------------- - // UNLOCK BUTTON — wired on the wallet landing page - // ------------------------------------------------------------------------- - - document.addEventListener('DOMContentLoaded', function () { - var unlockBtn = document.getElementById('g1wallet-unlock-btn'); - var lockedView = document.getElementById('g1wallet-locked-view'); - var unlockedView = document.getElementById('g1wallet-unlocked-view'); - var spinner = document.getElementById('g1wallet-unlock-spinner'); - var lockBtn = document.getElementById('g1wallet-lock-btn'); - - if (!unlockBtn) return; // Not on the wallet page. - - unlockBtn.addEventListener('click', function () { - var mnemonicEl = document.getElementById('g1wallet-mnemonic'); - var mnemonic = (mnemonicEl || {}).value || ''; - - // Normalize whitespace - mnemonic = mnemonic.trim().replace(/\s+/g, ' ').toLowerCase(); - - _clearError(); - - if (!mnemonic) { - _showError('Mnemonic phrase is required.'); - return; - } - - var words = mnemonic.split(' '); - if (words.length !== 12) { - _showError('Mnemonic phrase must be exactly 12 words. You entered ' + words.length + '.'); - return; - } - - if (!window.bip39 || typeof window.bip39.validateMnemonic !== 'function') { - _showError('Mnemonic validation library not available. Please reload the page.'); - return; - } - - if (!window.bip39.validateMnemonic(mnemonic)) { - _showError('Invalid mnemonic phrase. Check the words and try again.'); - return; - } - - // Clear the mnemonic field immediately after validation — - // it must not persist in the DOM any longer than necessary. - if (mnemonicEl) mnemonicEl.value = ''; - - if (spinner) spinner.style.display = 'inline'; - unlockBtn.disabled = true; - - _deriveKeypair(mnemonic).then(function (result) { - if (spinner) spinner.style.display = 'none'; - unlockBtn.disabled = false; - _unlockWallet(result.pubkeyAddress, result.pubkeyHex, result.cryptoKey); - }).catch(function (err) { - if (spinner) spinner.style.display = 'none'; - unlockBtn.disabled = false; - _showError('Key derivation failed: ' + err.message); - }); - }); - - if (lockBtn) { - lockBtn.addEventListener('click', function () { - _lockWallet(); - if (lockedView) lockedView.style.display = ''; - if (unlockedView) unlockedView.style.display = 'none'; - }); - } - - window.addEventListener('g1wallet:unlocked', function (e) { - if (lockedView) lockedView.style.display = 'none'; - if (unlockedView) unlockedView.style.display = ''; - var pubkeyDisplay = document.getElementById('g1wallet-pubkey-display'); - if (pubkeyDisplay) pubkeyDisplay.textContent = e.detail.pubkey || '—'; - - // POST pubkey to server for storage in channel settings. - // Only fires once after derivation; server deduplicates. - _postPubkeyToServer(e.detail.pubkey); - }); - }); - - // ------------------------------------------------------------------------- - // PUBKEY POST — stores pubkey in channel settings via fetch() - // Called once after each successful wallet unlock. - // ------------------------------------------------------------------------- - - function _postPubkeyToServer(pubkey) { - var form = document.getElementById('g1wallet-pubkey-form'); - if (!form) return; - var csrfInput = form.querySelector('input[name="g1wallet_csrf"]'); - var pubkeyInput = document.getElementById('g1wallet-pubkey-input'); - if (!csrfInput || !pubkeyInput) return; - - pubkeyInput.value = pubkey; - - var formData = new FormData(); - formData.append('g1wallet_csrf', csrfInput.value); - formData.append('g1_pubkey', pubkey); - - fetch('/g1wallet/pubkey', { - method: 'POST', - body: formData, - credentials: 'same-origin' - }).then(function (response) { - return response.json(); - }).then(function (data) { - if (data.status !== 'ok') { - console.warn('[g1wallet] pubkey store: server returned status=' + data.status); - } - }).catch(function (err) { - // Non-fatal: pubkey storage failure does not block wallet use. - console.warn('[g1wallet] pubkey store fetch error:', err.message); - }); - } - - // ------------------------------------------------------------------------- - // INTERNAL HELPERS - // ------------------------------------------------------------------------- - - function _unlockWallet(pubkeyAddress, pubkeyHex, cryptoKey) { - _session.unlocked = true; - _session.pubkey = pubkeyAddress; - _session.pubkeyHex = pubkeyHex; - _session._cryptoKey = cryptoKey; - window.dispatchEvent(new CustomEvent('g1wallet:unlocked', { - detail: { pubkey: pubkeyAddress, pubkeyHex: pubkeyHex } - })); - } - - function _lockWallet() { - _session.unlocked = false; - _session.pubkey = null; - _session.pubkeyHex = null; - _session._cryptoKey = null; - window.dispatchEvent(new CustomEvent('g1wallet:locked')); - } - - function _showError(message) { - var el = document.getElementById('g1wallet-unlock-error'); - if (el) { - el.textContent = message; - el.style.display = ''; - } - } - - function _clearError() { - var el = document.getElementById('g1wallet-unlock-error'); - if (el) { - el.textContent = ''; - el.style.display = 'none'; - } - } - - function _escHtml(str) { - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); - } - - // ------------------------------------------------------------------------- - // AUTO-POPULATE: cry01 and poll01 form fields - // Elements with data-g1wallet-target="pubkey" are populated on unlock. - // ------------------------------------------------------------------------- - - window.addEventListener('g1wallet:unlocked', function (e) { - var pubkey = e.detail.pubkey || ''; - var targets = document.querySelectorAll('[data-g1wallet-target="pubkey"]'); - targets.forEach(function (el) { el.value = pubkey; }); - }); - - window.addEventListener('g1wallet:locked', function () { - var targets = document.querySelectorAll('[data-g1wallet-target="pubkey"]'); - targets.forEach(function (el) { el.value = ''; }); - }); + // When the wallet page stores a g1 address server-side, other addons + // (scn01, dsc01) can read it via a data attribute on the page body + // or a meta tag — no JS session state needed at this stage. }());