Updated
This commit is contained in:
442
hubzilla/addon/g1wallet/view/css/g1wallet.js
Normal file
442
hubzilla/addon/g1wallet/view/css/g1wallet.js
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
/**
|
||||||
|
* g1wallet.js — Ğ1 Wallet client-side implementation.
|
||||||
|
*
|
||||||
|
* DERIVATION (Substrate / Duniter v2 / Ğecko standard):
|
||||||
|
* 12-word BIP39 mnemonic
|
||||||
|
* → mnemonicToEntropy() → 16 bytes of raw entropy (hex)
|
||||||
|
* → zero-pad right to 32 bytes → Ed25519 seed (the Substrate "mini-secret")
|
||||||
|
* → nacl.sign.keyPair.fromSeed(seed) → { publicKey: Uint8Array(32) }
|
||||||
|
* → SS58-encode publicKey with Ğ1 prefix bytes [0x58, 0x91] → g1... address
|
||||||
|
*
|
||||||
|
* Signing uses SubtleCrypto (Ed25519, non-extractable CryptoKey).
|
||||||
|
* Key derivation uses TweetNaCl (nacl-fast.min.js, vendored, pinned v1.0.3).
|
||||||
|
*
|
||||||
|
* CRITICAL RULES:
|
||||||
|
* - 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// SESSION STATE — module-scope memory only, gone on page close
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var _session = {
|
||||||
|
unlocked: false,
|
||||||
|
pubkey: null, // SS58 g1... address
|
||||||
|
pubkeyHex: null, // 32-byte pubkey as hex
|
||||||
|
_cryptoKey: null // SubtleCrypto CryptoKey (non-extractable Ed25519)
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// SS58 ENCODING
|
||||||
|
// Ğ1 prefix bytes: [0x58, 0x91] (verified against live mainnet address)
|
||||||
|
// Format: prefix(2) + pubkey(32) + checksum(2) = 36 bytes, base58-encoded
|
||||||
|
// Checksum: first 2 bytes of Blake2b-512("SS58PRE" + prefix + pubkey)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var _BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
function _base58Encode(bytes) {
|
||||||
|
var zeros = 0;
|
||||||
|
for (var i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;
|
||||||
|
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 — minimal synchronous pure-JS implementation.
|
||||||
|
// Used only for SS58 checksum (2 bytes of a 64-byte hash).
|
||||||
|
var _blake2b512 = (function () {
|
||||||
|
var IV = new Uint32Array([
|
||||||
|
0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85,
|
||||||
|
0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A,
|
||||||
|
0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C,
|
||||||
|
0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19
|
||||||
|
]);
|
||||||
|
var SIGMA = [
|
||||||
|
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
|
||||||
|
];
|
||||||
|
|
||||||
|
function G(v, a, b, c, d, x0, x1, y0, y1) {
|
||||||
|
// 64-bit add via 32-bit pairs (hi, lo)
|
||||||
|
function add64(ah, al, bh, bl) {
|
||||||
|
var lo = (al >>> 0) + (bl >>> 0);
|
||||||
|
return { h: (ah + bh + (lo / 0x100000000 | 0)) >>> 0, l: lo >>> 0 };
|
||||||
|
}
|
||||||
|
function rotr64(h, l, r) {
|
||||||
|
if (r < 32) return { h: (h >>> r) | (l << (32-r)), l: (l >>> r) | (h << (32-r)) };
|
||||||
|
return { h: (l >>> (r-32)) | (h << (64-r)), l: (h >>> (r-32)) | (l << (64-r)) };
|
||||||
|
}
|
||||||
|
var va = {h:v[a*2],l:v[a*2+1]}, vb = {h:v[b*2],l:v[b*2+1]};
|
||||||
|
var vc = {h:v[c*2],l:v[c*2+1]}, vd = {h:v[d*2],l:v[d*2+1]};
|
||||||
|
var mx = {h:x0,l:x1}, my = {h:y0,l:y1};
|
||||||
|
va = add64(va.h,va.l, vb.h,vb.l); va = add64(va.h,va.l, mx.h,mx.l);
|
||||||
|
vd = rotr64(vd.h^va.h, vd.l^va.l, 32);
|
||||||
|
vc = add64(vc.h,vc.l, vd.h,vd.l);
|
||||||
|
vb = rotr64(vb.h^vc.h, vb.l^vc.l, 24);
|
||||||
|
va = add64(va.h,va.l, vb.h,vb.l); va = add64(va.h,va.l, my.h,my.l);
|
||||||
|
vd = rotr64(vd.h^va.h, vd.l^va.l, 16);
|
||||||
|
vc = add64(vc.h,vc.l, vd.h,vd.l);
|
||||||
|
vb = rotr64(vb.h^vc.h, vb.l^vc.l, 63);
|
||||||
|
v[a*2]=va.h; v[a*2+1]=va.l; v[b*2]=vb.h; v[b*2+1]=vb.l;
|
||||||
|
v[c*2]=vc.h; v[c*2+1]=vc.l; v[d*2]=vd.h; v[d*2+1]=vd.l;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compress(h, m, t, last) {
|
||||||
|
var v = new Uint32Array(32);
|
||||||
|
for (var i = 0; i < 16; i++) v[i] = h[i];
|
||||||
|
for (var i = 0; i < 16; i++) v[16+i] = IV[i];
|
||||||
|
v[24] ^= t & 0xffffffff; v[25] ^= Math.floor(t / 0x100000000);
|
||||||
|
if (last) { v[28] = ~v[28]; v[29] = ~v[29]; }
|
||||||
|
for (var r = 0; r < 12; r++) {
|
||||||
|
var s = SIGMA.slice(r*16, r*16+16);
|
||||||
|
G(v,0,4,8,12, m[s[0]*2],m[s[0]*2+1], m[s[1]*2],m[s[1]*2+1]);
|
||||||
|
G(v,1,5,9,13, m[s[2]*2],m[s[2]*2+1], m[s[3]*2],m[s[3]*2+1]);
|
||||||
|
G(v,2,6,10,14, m[s[4]*2],m[s[4]*2+1], m[s[5]*2],m[s[5]*2+1]);
|
||||||
|
G(v,3,7,11,15, m[s[6]*2],m[s[6]*2+1], m[s[7]*2],m[s[7]*2+1]);
|
||||||
|
G(v,0,5,10,15, m[s[8]*2],m[s[8]*2+1], m[s[9]*2],m[s[9]*2+1]);
|
||||||
|
G(v,1,6,11,12, m[s[10]*2],m[s[10]*2+1], m[s[11]*2],m[s[11]*2+1]);
|
||||||
|
G(v,2,7,8,13, m[s[12]*2],m[s[12]*2+1], m[s[13]*2],m[s[13]*2+1]);
|
||||||
|
G(v,3,4,9,14, m[s[14]*2],m[s[14]*2+1], m[s[15]*2],m[s[15]*2+1]);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 16; i++) h[i] ^= v[i] ^ v[16+i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash(input) {
|
||||||
|
var h = new Uint32Array(16);
|
||||||
|
for (var i = 0; i < 16; i++) h[i] = IV[i];
|
||||||
|
h[0] ^= 0x01010040; // fanout=1, depth=1, outlen=64
|
||||||
|
|
||||||
|
var buf = new Uint8Array(128), buflen = 0, total = 0;
|
||||||
|
|
||||||
|
function update(data) {
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
if (buflen === 128) {
|
||||||
|
total += 128;
|
||||||
|
var m = new Uint32Array(32);
|
||||||
|
for (var j = 0; j < 32; j++)
|
||||||
|
m[j] = buf[j*4] | (buf[j*4+1]<<8) | (buf[j*4+2]<<16) | (buf[j*4+3]<<24);
|
||||||
|
compress(h, m, total, false);
|
||||||
|
buflen = 0;
|
||||||
|
}
|
||||||
|
buf[buflen++] = data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(input);
|
||||||
|
total += buflen;
|
||||||
|
while (buflen < 128) buf[buflen++] = 0;
|
||||||
|
var m = new Uint32Array(32);
|
||||||
|
for (var j = 0; j < 32; j++)
|
||||||
|
m[j] = buf[j*4] | (buf[j*4+1]<<8) | (buf[j*4+2]<<16) | (buf[j*4+3]<<24);
|
||||||
|
compress(h, m, total, true);
|
||||||
|
|
||||||
|
var out = new Uint8Array(64);
|
||||||
|
for (var i = 0; i < 16; i++) {
|
||||||
|
out[i*4] = h[i] & 0xff;
|
||||||
|
out[i*4+1] = (h[i] >>> 8) & 0xff;
|
||||||
|
out[i*4+2] = (h[i] >>> 16)& 0xff;
|
||||||
|
out[i*4+3] = (h[i] >>> 24)& 0xff;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hash: hash };
|
||||||
|
})();
|
||||||
|
|
||||||
|
function _ss58Encode(pubkeyBytes) {
|
||||||
|
// Ğ1 prefix bytes [0x58, 0x91] — verified against live mainnet.
|
||||||
|
var prefixBytes = new Uint8Array([0x58, 0x91]);
|
||||||
|
var payload = new Uint8Array(34);
|
||||||
|
payload.set(prefixBytes, 0);
|
||||||
|
payload.set(pubkeyBytes, 2);
|
||||||
|
|
||||||
|
var ss58pre = new TextEncoder().encode('SS58PRE');
|
||||||
|
var checksumInput = new Uint8Array(ss58pre.length + payload.length);
|
||||||
|
checksumInput.set(ss58pre, 0);
|
||||||
|
checksumInput.set(payload, ss58pre.length);
|
||||||
|
|
||||||
|
var hashBytes = _blake2b512.hash(checksumInput);
|
||||||
|
var full = new Uint8Array(36);
|
||||||
|
full.set(prefixBytes, 0);
|
||||||
|
full.set(pubkeyBytes, 2);
|
||||||
|
full.set(hashBytes.slice(0, 2), 34);
|
||||||
|
return _base58Encode(full);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hexToBytes(hex) {
|
||||||
|
var b = new Uint8Array(hex.length / 2);
|
||||||
|
for (var i = 0; i < hex.length; i += 2) b[i/2] = parseInt(hex.slice(i,i+2), 16);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bytesToHex(bytes) {
|
||||||
|
var s = '';
|
||||||
|
for (var i = 0; i < bytes.length; i++) s += ('0' + bytes[i].toString(16)).slice(-2);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// KEY DERIVATION
|
||||||
|
// Uses TweetNaCl (nacl-fast.min.js) for Ed25519 pubkey derivation.
|
||||||
|
// nacl.sign.keyPair.fromSeed(seed) is the correct primitive:
|
||||||
|
// seed (32 bytes) → { publicKey: Uint8Array(32), secretKey: Uint8Array(64) }
|
||||||
|
// The secretKey is discarded — signing uses SubtleCrypto with a
|
||||||
|
// non-extractable CryptoKey imported from the same seed.
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function _deriveKeypair(mnemonic) {
|
||||||
|
if (!window.nacl || typeof window.nacl.sign === 'undefined') {
|
||||||
|
return Promise.reject(new Error('TweetNaCl library not loaded. Please reload the page.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var entropyHex = window.bip39.mnemonicToEntropy(mnemonic);
|
||||||
|
var entropy = _hexToBytes(entropyHex); // 16 bytes for 12-word mnemonic
|
||||||
|
|
||||||
|
// Substrate mini-secret: entropy zero-padded right to 32 bytes.
|
||||||
|
var seed32 = new Uint8Array(32);
|
||||||
|
seed32.set(entropy, 0);
|
||||||
|
|
||||||
|
// Derive Ed25519 pubkey via TweetNaCl.
|
||||||
|
var keypair = window.nacl.sign.keyPair.fromSeed(seed32);
|
||||||
|
var pubkeyBytes = keypair.publicKey; // Uint8Array(32)
|
||||||
|
var pubkeyHex = _bytesToHex(pubkeyBytes);
|
||||||
|
var pubkeyAddress = _ss58Encode(pubkeyBytes);
|
||||||
|
|
||||||
|
// Import seed as non-extractable Ed25519 CryptoKey for signing.
|
||||||
|
// PKCS8 DER wrapper for Ed25519 (RFC 5958):
|
||||||
|
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, ['sign']
|
||||||
|
).then(function (cryptoKey) {
|
||||||
|
return { pubkeyAddress: pubkeyAddress, pubkeyHex: pubkeyHex, cryptoKey: cryptoKey };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// PUBLIC API
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
window.g1wallet = {
|
||||||
|
isUnlocked: function () { return _session.unlocked; },
|
||||||
|
getPubkey: function () { return _session.unlocked ? _session.pubkey : null; },
|
||||||
|
getPubkeyHex:function () { return _session.unlocked ? _session.pubkeyHex : null; },
|
||||||
|
|
||||||
|
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) {
|
||||||
|
window.dispatchEvent(new CustomEvent(callback, {
|
||||||
|
detail: { signature: _bytesToHex(new Uint8Array(sigBuf)), pubkey: _session.pubkey, pubkeyHex: _session.pubkeyHex }
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
window.dispatchEvent(new CustomEvent(callback, { detail: { error: err.message } }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// WIDGET API
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
window.G1WalletWidget = {
|
||||||
|
init: function (uid, walletUrl) {
|
||||||
|
window.addEventListener('g1wallet:unlocked', function (e) {
|
||||||
|
var el = document.getElementById(uid + '-status');
|
||||||
|
if (!el) return;
|
||||||
|
var short = (e.detail.pubkey || '').substring(0, 12) + '…';
|
||||||
|
el.innerHTML = '<div class="g1wallet-status g1wallet-status-unlocked">' +
|
||||||
|
'<span class="g1wallet-status-indicator">🔓</span> ' +
|
||||||
|
'<span class="text-success small">Unlocked</span>' +
|
||||||
|
'<div class="g1wallet-cached-key text-muted small font-monospace mt-1">' + _escHtml(short) + '</div></div>';
|
||||||
|
});
|
||||||
|
window.addEventListener('g1wallet:locked', function () {
|
||||||
|
var el = document.getElementById(uid + '-status');
|
||||||
|
if (!el) return;
|
||||||
|
el.innerHTML = '<div class="g1wallet-status g1wallet-status-locked">' +
|
||||||
|
'<span class="g1wallet-status-indicator">🔒</span> ' +
|
||||||
|
'<span class="text-muted small">Locked</span>' +
|
||||||
|
'<div class="mt-1"><a href="' + _escHtml(walletUrl) + '" class="btn btn-xs btn-outline-secondary btn-sm">Unlock</a></div></div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// UNLOCK BUTTON
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
unlockBtn.addEventListener('click', function () {
|
||||||
|
var mnemonicEl = document.getElementById('g1wallet-mnemonic');
|
||||||
|
var mnemonic = (mnemonicEl || {}).value || '';
|
||||||
|
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 mnemonic field immediately after validation.
|
||||||
|
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 pd = document.getElementById('g1wallet-pubkey-display');
|
||||||
|
if (pd) pd.textContent = e.detail.pubkey || '—';
|
||||||
|
_postPubkeyToServer(e.detail.pubkey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// PUBKEY POST
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 fd = new FormData();
|
||||||
|
fd.append('g1wallet_csrf', csrfInput.value);
|
||||||
|
fd.append('g1_pubkey', pubkey);
|
||||||
|
fetch('/g1wallet/pubkey', { method: 'POST', body: fd, credentials: 'same-origin' })
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (d) { if (d.status !== 'ok') console.warn('[g1wallet] pubkey store:', d.status); })
|
||||||
|
.catch(function (e) { console.warn('[g1wallet] pubkey store error:', e.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(msg) {
|
||||||
|
var el = document.getElementById('g1wallet-unlock-error');
|
||||||
|
if (el) { el.textContent = msg; el.style.display = ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function _clearError() {
|
||||||
|
var el = document.getElementById('g1wallet-unlock-error');
|
||||||
|
if (el) { el.textContent = ''; el.style.display = 'none'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function _escHtml(s) {
|
||||||
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-populate data-g1wallet-target="pubkey" fields on other addons.
|
||||||
|
window.addEventListener('g1wallet:unlocked', function (e) {
|
||||||
|
document.querySelectorAll('[data-g1wallet-target="pubkey"]')
|
||||||
|
.forEach(function (el) { el.value = e.detail.pubkey || ''; });
|
||||||
|
});
|
||||||
|
window.addEventListener('g1wallet:locked', function () {
|
||||||
|
document.querySelectorAll('[data-g1wallet-target="pubkey"]')
|
||||||
|
.forEach(function (el) { el.value = ''; });
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
||||||
Reference in New Issue
Block a user