Wallet-verified member messaging (matrix-lines permissions, admin visibility)
- messages.js: challenge/personal_sign/recover auth (vendored pinned js-sha3 0.9.3 + noble-secp256k1 1.7.1, server-side only; self-tested positive + tamper cases), 30d HttpOnly sessions, message store on the volume, matrix-line permissions (your downline direct or broadcast, your upline chain - nothing else, so spam is impossible by construction), daily rate limits (30 direct / 3 broadcasts), 1500-char plain text - chain.js: memberIdByAccount (wallet -> position for sign-in) - API: msg-challenge/-verify/-me/-inbox/-send/-read public + msg-unread (count only, no auth) + admin/messages (full visibility, disclosed to members in the UI) - Dashboard: Messages card with unread bell, one-tap wallet sign-in, inbox with auto-read, compose with to-ID or whole-team broadcast - Admin: Member Messages review table - Chatbot canned answer + AI system prompt updated Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -667,6 +667,15 @@ function getOrgShare(rootId) {
|
||||
};
|
||||
}
|
||||
|
||||
// Wallet address -> position id (for wallet-verified messaging sign-in).
|
||||
// One position per address by contract design.
|
||||
function memberIdByAccount(address) {
|
||||
if (!state || !address) return null;
|
||||
const a = String(address).toLowerCase();
|
||||
for (const [id, m] of Object.entries(state.members)) if (m.account === a) return Number(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Company-rotation pick: breadth-first (matrix order, left→right) first
|
||||
// position under `rootId` that still needs directs — the "next open team
|
||||
// position" for the public rotation when publicRotationMode==='chain'.
|
||||
@@ -735,4 +744,4 @@ async function getIncome(id) {
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { startIndexer, getPayoutsPublic, verifyMember, memberLookup, memberPublic, getIncome, getOwnerUpgradeNeeds, getOrgRouting, getOrgShare, getCoachingScan, getMatrixTree, isInTeam, nextOpenPosition, balanceOf, CONTRACT };
|
||||
module.exports = { startIndexer, getPayoutsPublic, verifyMember, memberLookup, memberPublic, getIncome, getOwnerUpgradeNeeds, getOrgRouting, getOrgShare, getCoachingScan, getMatrixTree, isInTeam, nextOpenPosition, memberIdByAccount, balanceOf, CONTRACT };
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// RM Circle — wallet-verified member messaging.
|
||||
// Identity = the wallet that owns a position (proved by a free personal_sign,
|
||||
// verified server-side via vendored keccak256 + secp256k1 recovery — the two
|
||||
// files in ./vendor are pinned; see the commit that added them).
|
||||
// Permission = the matrix: you may message your own downline (direct or
|
||||
// broadcast) and your own upline chain. Nothing else. Admin can review all
|
||||
// messages (disclosed in the UI).
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const secp = require('./vendor/secp256k1.js');
|
||||
const sha3 = require('./vendor/sha3.js');
|
||||
const keccak256 = sha3.keccak256;
|
||||
|
||||
secp.utils.hmacSha256Sync = (key, ...msgs) => {
|
||||
const h = crypto.createHmac('sha256', Buffer.from(key));
|
||||
msgs.forEach(m => h.update(Buffer.from(m)));
|
||||
return Uint8Array.from(h.digest());
|
||||
};
|
||||
|
||||
const SESSION_TTL = 30 * 24 * 3600 * 1000;
|
||||
const CHALLENGE_TTL = 10 * 60 * 1000;
|
||||
const MAX_BODY = 1500;
|
||||
const MAX_STORE = 5000;
|
||||
const DAILY_DIRECT = 30, DAILY_BROADCAST = 3;
|
||||
|
||||
let DATA_DIR = null, chain = null, IS_PROD = false;
|
||||
let sessions = new Map(); // token -> {address, id, expires}
|
||||
const challenges = new Map(); // addressLower -> {message, exp}
|
||||
|
||||
const MSG_FILE = () => path.join(DATA_DIR, 'messages.json');
|
||||
const SESS_FILE = () => path.join(DATA_DIR, 'msg-sessions.json');
|
||||
function readJson(f, fb) { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch (e) { return fb; } }
|
||||
function writeJson(f, v) { fs.writeFileSync(f, JSON.stringify(v)); }
|
||||
function loadSessions() { const o = readJson(SESS_FILE(), {}); sessions = new Map(Object.entries(o).filter(([, s]) => s.expires > Date.now())); }
|
||||
function saveSessions() { writeJson(SESS_FILE(), Object.fromEntries(sessions)); }
|
||||
function getMessages() { return readJson(MSG_FILE(), []); }
|
||||
function saveMessages(m) { writeJson(MSG_FILE(), m.slice(-MAX_STORE)); }
|
||||
|
||||
function init(opts) { DATA_DIR = opts.dataDir; chain = opts.chain; IS_PROD = !!opts.isProd; loadSessions(); }
|
||||
|
||||
// ---- crypto ----
|
||||
function personalDigest(msg) {
|
||||
const m = Buffer.from(msg, 'utf8');
|
||||
const pre = Buffer.from('\x19Ethereum Signed Message:\n' + m.length, 'utf8');
|
||||
return Buffer.from(keccak256(Buffer.concat([pre, m])), 'hex');
|
||||
}
|
||||
function recoverAddress(msg, signature) {
|
||||
const raw = Buffer.from(String(signature).replace(/^0x/, ''), 'hex');
|
||||
if (raw.length !== 65) throw new Error('Bad signature length');
|
||||
let v = raw[64]; if (v >= 27) v -= 27;
|
||||
if (v !== 0 && v !== 1) throw new Error('Bad signature recovery byte');
|
||||
const pub = secp.recoverPublicKey(personalDigest(msg), raw.slice(0, 64), v, false);
|
||||
return '0x' + keccak256(Buffer.from(pub.slice(1))).slice(-40);
|
||||
}
|
||||
|
||||
// ---- auth ----
|
||||
const ADDR_RE = /^0x[0-9a-fA-F]{40}$/;
|
||||
function makeChallenge(address) {
|
||||
const a = address.toLowerCase();
|
||||
const nonce = crypto.randomBytes(16).toString('hex');
|
||||
const message = `RM Circle member sign-in\n\nWallet: ${a}\nNonce: ${nonce}\n\nSigning is free, proves you own this wallet to rmcircle.team, and cannot move funds or approve anything.`;
|
||||
challenges.set(a, { message, exp: Date.now() + CHALLENGE_TTL });
|
||||
return message;
|
||||
}
|
||||
function verifyChallenge(address, signature) {
|
||||
const a = address.toLowerCase();
|
||||
const ch = challenges.get(a);
|
||||
if (!ch || ch.exp < Date.now()) return { error: 'Challenge expired - tap sign-in again.' };
|
||||
let rec;
|
||||
try { rec = recoverAddress(ch.message, signature); } catch (e) { return { error: 'Invalid signature: ' + e.message }; }
|
||||
if (rec !== a) return { error: 'Signature does not match this wallet.' };
|
||||
challenges.delete(a);
|
||||
const id = chain.memberIdByAccount(a);
|
||||
if (!id) return { error: 'No RM Circle position is registered to this wallet.' };
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
sessions.set(token, { address: a, id, expires: Date.now() + SESSION_TTL });
|
||||
saveSessions();
|
||||
return { token, id };
|
||||
}
|
||||
function authFromCookie(req) {
|
||||
const m = /(?:^|;\s*)ctb\.msid=([^;]+)/.exec(req.headers.cookie || '');
|
||||
if (!m) return null;
|
||||
const s = sessions.get(decodeURIComponent(m[1]));
|
||||
if (!s || s.expires < Date.now()) return null;
|
||||
return s;
|
||||
}
|
||||
function sessionCookie(token) {
|
||||
return `ctb.msid=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_TTL / 1000}${IS_PROD ? '; Secure' : ''}`;
|
||||
}
|
||||
|
||||
// ---- permissions ----
|
||||
function canMessage(fromId, toId) {
|
||||
if (fromId === toId) return false;
|
||||
return chain.isInTeam(toId, fromId) || chain.isInTeam(fromId, toId);
|
||||
}
|
||||
function isRecipient(msg, myId) {
|
||||
if (msg.toId === myId) return true;
|
||||
if (msg.org && msg.fromId !== myId && chain.isInTeam(myId, msg.fromId)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- operations ----
|
||||
function sanitizeBody(b) {
|
||||
return String(b || '').replace(/\r/g, '').replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, '').trim().slice(0, MAX_BODY);
|
||||
}
|
||||
function send(sess, body) {
|
||||
const text = sanitizeBody(body.body);
|
||||
if (!text) return { error: 'Write a message first.' };
|
||||
const org = !!body.org;
|
||||
const msgs = getMessages();
|
||||
const dayAgo = Date.now() - 24 * 3600 * 1000;
|
||||
const mine = msgs.filter(m => m.fromId === sess.id && m.ts > dayAgo);
|
||||
if (org && mine.filter(m => m.org).length >= DAILY_BROADCAST) return { error: `Broadcast limit reached (${DAILY_BROADCAST}/day).` };
|
||||
if (!org && mine.filter(m => !m.org).length >= DAILY_DIRECT) return { error: `Daily message limit reached (${DAILY_DIRECT}/day).` };
|
||||
let toId = 0;
|
||||
if (!org) {
|
||||
toId = Number(body.toId);
|
||||
if (!Number.isInteger(toId) || toId < 1) return { error: 'Enter the member ID to send to.' };
|
||||
if (!canMessage(sess.id, toId)) return { error: `#${toId} is not in your team or upline - messaging follows your matrix lines only.` };
|
||||
}
|
||||
const rec = { mid: crypto.randomBytes(8).toString('hex'), fromId: sess.id, toId, org, body: text, ts: Date.now(), read: {} };
|
||||
msgs.push(rec);
|
||||
saveMessages(msgs);
|
||||
return { ok: true, mid: rec.mid };
|
||||
}
|
||||
function inbox(sess) {
|
||||
const msgs = getMessages();
|
||||
const forMe = [], sent = [];
|
||||
for (const m of msgs) {
|
||||
if (m.fromId === sess.id) sent.push(m);
|
||||
else if (isRecipient(m, sess.id)) forMe.push(m);
|
||||
}
|
||||
const shape = (m, mine) => ({ mid: m.mid, fromId: m.fromId, toId: m.toId || null, org: !!m.org, body: m.body, ts: m.ts, read: mine ? undefined : !!(m.read && m.read[sess.id]), mine });
|
||||
return {
|
||||
id: sess.id,
|
||||
inbox: forMe.slice(-100).reverse().map(m => shape(m, false)),
|
||||
sent: sent.slice(-30).reverse().map(m => shape(m, true))
|
||||
};
|
||||
}
|
||||
function markRead(sess, mids) {
|
||||
const msgs = getMessages();
|
||||
let changed = false;
|
||||
for (const m of msgs) {
|
||||
if (mids.includes(m.mid) && isRecipient(m, sess.id) && !(m.read && m.read[sess.id])) {
|
||||
m.read = m.read || {}; m.read[sess.id] = Date.now(); changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) saveMessages(msgs);
|
||||
return { ok: true };
|
||||
}
|
||||
function unreadCount(id) {
|
||||
if (!Number.isInteger(id) || id < 1) return 0;
|
||||
return getMessages().filter(m => isRecipient(m, id) && !(m.read && m.read[id])).length;
|
||||
}
|
||||
function adminList() {
|
||||
return getMessages().slice(-300).reverse().map(m => ({ mid: m.mid, fromId: m.fromId, toId: m.toId || null, org: !!m.org, body: m.body, ts: m.ts, readCount: Object.keys(m.read || {}).length }));
|
||||
}
|
||||
|
||||
module.exports = { init, makeChallenge, verifyChallenge, authFromCookie, sessionCookie, send, inbox, markRead, unreadCount, adminList, ADDR_RE };
|
||||
@@ -6,6 +6,7 @@
|
||||
<div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">Member ID Submissions</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">New members who confirmed their purchase on the start page. Each one was posted to your Hermes Telegram chat — add them to the rotation.</p></div><div class="table-wrap"><table class="table"><thead><tr><th>When</th><th>Name / Handle</th><th>New ID</th><th>Joined under</th><th>Source</th><th>On-chain</th></tr></thead><tbody id="submissionRows"></tbody></table></div></div>
|
||||
<div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Your Organization vs. the Network</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">How your team — rooted at your top ID — stacks up against the entire RM Circle smart contract. Live on-chain.</p></div><form id="orgShareForm" style="display:flex;gap:8px"><input id="orgRoot" class="input" style="max-width:110px" placeholder="21" inputmode="numeric"><button class="btn btn-secondary btn-sm">Refresh</button></form></div><div id="orgShare"><div class="empty">Reading the blockchain…</div></div></div>
|
||||
<div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Coaching Radar</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Live triage of your whole org: who to nudge, what to tell them, and how much POL is on the line. Computed fresh from the chain index on every refresh.</p></div><button id="coachRefresh" class="btn btn-secondary btn-sm">Refresh</button></div><div id="coachOut"><div class="empty">Loading…</div></div></div>
|
||||
<div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Member Messages</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Wallet-verified member-to-member messages, newest first. Admin can review for abuse — members are told this in the UI.</p></div><button id="msgRefresh" class="btn btn-secondary btn-sm">Refresh</button></div><div class="table-wrap"><table class="table"><thead><tr><th>When</th><th>From</th><th>To</th><th>Message</th><th>Read by</th></tr></thead><tbody id="msgRows"><tr><td colspan="5" class="empty">Loading…</td></tr></tbody></table></div></div>
|
||||
<div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">My Positions — Income</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Every payment received by your own positions, live from the contract. Comma-separated IDs — saved for next time.</p></div><form id="incomeForm" style="display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap"><input id="incomeIds" class="input" style="max-width:260px" placeholder="21,24,25" inputmode="numeric"><button class="btn btn-teal">Load</button></form><div id="incomeAlert"></div><div id="incomeRouting" style="margin-bottom:14px"></div><div id="incomeSummary" class="facts" style="grid-template-columns:repeat(4,1fr);margin-bottom:12px"></div><div id="incomeResult"></div></div>
|
||||
<div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">On-Chain Member Lookup</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Enter an RM Circle ID to read its registration, lineage, and every payment it has received — live from the smart contract.</p></div><form id="lookupForm" style="display:flex;gap:10px;margin-bottom:14px"><input id="lookupId" class="input" style="max-width:220px" placeholder="Member ID e.g. 46" inputmode="numeric"><button class="btn btn-teal">Look Up</button></form><div id="lookupResult"></div></div>
|
||||
<div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Matrix View</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">The entire on-chain matrix — who landed where, with tier, level, directs, and earnings per position. Click a position to drill down.</p></div><div style="display:flex;gap:8px"><button id="treeLoadBtn" class="btn btn-secondary btn-sm">Load Matrix</button><button id="treeToggleBtn" class="btn btn-secondary btn-sm hidden">List view</button></div></div><div id="matrixNav" class="hidden" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"></div><div id="matrixTree"></div></div></div>
|
||||
|
||||
@@ -41,6 +41,7 @@ function render(){
|
||||
const orgEl=document.getElementById('orgRoot');if(orgEl&&!orgEl.value)orgEl.value=state.config.orgRootId||'21';
|
||||
if(!orgShareAutoLoaded&&orgEl&&orgEl.value){orgShareAutoLoaded=true;loadOrgShare();}
|
||||
if(!coachAutoLoaded){coachAutoLoaded=true;loadCoaching();}
|
||||
if(!msgsAutoLoaded){msgsAutoLoaded=true;loadAdminMsgs();}
|
||||
const efi=document.getElementById('emailFromInput');if(efi&&!efi.value)efi.value=state.config.emailFrom||em.from||'';
|
||||
const f=document.getElementById('configForm'),c=state.config;for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','teamRootId','teamAlertEmail','ownerAlertEmail'])if(f.elements[k])f.elements[k].value=c[k]??'';f.elements.showSponsorName.checked=!!c.showSponsorName;f.elements.showQueueProgress.checked=!!c.showQueueProgress;
|
||||
}
|
||||
@@ -121,6 +122,15 @@ async function loadCoaching(){
|
||||
}
|
||||
const cr=document.getElementById('coachRefresh');if(cr)cr.addEventListener('click',loadCoaching);
|
||||
let coachAutoLoaded=false;
|
||||
async function loadAdminMsgs(){
|
||||
const tb=document.getElementById('msgRows');if(!tb)return;
|
||||
try{
|
||||
const d=await api('/api/admin/messages');
|
||||
tb.innerHTML=(d.messages||[]).map(m=>`<tr><td>${esc(new Date(m.ts).toLocaleString())}</td><td><strong>#${m.fromId}</strong></td><td>${m.org?'📣 whole team':'#'+m.toId}</td><td style="white-space:pre-wrap;max-width:460px">${esc(m.body)}</td><td>${m.readCount}</td></tr>`).join('')||'<tr><td colspan="5" class="empty">No messages yet.</td></tr>';
|
||||
}catch(x){tb.innerHTML=`<tr><td colspan="5" class="empty">${esc(x.message)}</td></tr>`}
|
||||
}
|
||||
const mr=document.getElementById('msgRefresh');if(mr)mr.addEventListener('click',loadAdminMsgs);
|
||||
let msgsAutoLoaded=false;
|
||||
let incomeAutoLoaded=false;
|
||||
async function loadIncome(){
|
||||
const ids=(document.getElementById('incomeIds').value||'').trim();
|
||||
|
||||
+2
-2
@@ -48,8 +48,8 @@
|
||||
a:()=>`Two ways money reaches your position — the full diagram is at <a href="/how-pay-works">rmcircle.team/how-pay-works</a>. <strong>(1) Entry rewards:</strong> when a direct joins under your link you get their entry reward (about 326 POL at Premium) and you keep it — on every direct you bring. <strong>(2) Upgrade payments:</strong> each person below you pays you once, at the ONE level that matches how far below you they sit — someone 1 layer below pays you when they hit Ascensus, 2 layers below pays you at Fabrica, 3 at Culmen, and so on. To catch each, you must be at that level yourself and qualified, so stay one level ahead of your team. No income is guaranteed.`},
|
||||
{k:['tier','standard','premium tier','standard tier','premium vs standard','which tier','half','smaller payment','less than expected','why is my payment'],
|
||||
a:()=>`There are two tiers. <strong>Premium</strong> is what our whole team builds at (${pol()} POL entry) — full payments. <strong>Standard</strong> costs about half and pays/earns half at every level. So if a payment ever comes in smaller than expected, it usually came from a Standard-tier position below you. Your tier is <strong>set when you join and can't be changed later</strong> (upgrading advances your level, not your tier), so always join <strong>Premium</strong> and make sure the people you bring on do too. Full breakdown: <a href="/how-pay-works">how-pay-works</a>.`},
|
||||
{k:['dashboard','my dashboard','my page','my position','my team','check my','see my','pipeline','my stats','alerts','notify me','email me','get notified'],
|
||||
a:()=>`Your <strong>Member Dashboard</strong> is at <a href="/my">rmcircle.team/my</a> — enter your ID to see your position, your team (with a depth summary showing members per generation), your payments, your pipeline (money forming below you), any spillover under you, and a <strong>Coach Your Team</strong> panel that tells you exactly who in your leg needs a nudge and what to say. You can also turn on <strong>email alerts</strong> there to be notified the moment you're paid or need to upgrade. To share, use your personal invite page: <strong>rmcircle.team/join/<your ID></strong>.`},
|
||||
{k:['dashboard','my dashboard','my page','my position','my team','check my','see my','pipeline','my stats','alerts','notify me','email me','get notified','message','messages','contact my sponsor','contact my upline','contact my downline','reach my team'],
|
||||
a:()=>`Your <strong>Member Dashboard</strong> is at <a href="/my">rmcircle.team/my</a> — enter your ID to see your position, your team (with a depth summary showing members per generation), your payments, your pipeline (money forming below you), any spillover under you, and a <strong>Coach Your Team</strong> panel that tells you exactly who in your leg needs a nudge and what to say. You can also turn on <strong>email alerts</strong> there, and use <strong>Messages</strong> — wallet-verified messaging with your upline and your team (sign in once with your wallet; no email needed; only people on your matrix lines can message you). To share, use your personal invite page: <strong>rmcircle.team/join/<your ID></strong>.`},
|
||||
{k:['level','levels','upgrade','scintilla','ascensus','fabrica','culmen','apex','fastigium','vertex','corona','8 levels'],
|
||||
a:()=>`There are 8 Premium levels: <strong>Scintilla, Ascensus, Fabrica, Culmen, Apex, Fastigium, Vertex, Corona</strong>. Everyone starts at Scintilla. Upgrade as quickly as practical — ideally using earned POL — because the first two payments at each level are designed to help fund your next upgrade. Stay aware of your active downline's levels so you don't fall behind.`},
|
||||
{k:['30 positions','goal','milestone','matrix','how many people','team size'],
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@
|
||||
<div class="table-card" style="margin-bottom:18px"><div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap"><div><h2 style="margin:0 0 4px">Your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 14px">Your position's matrix — the rollup line on each card counts everyone underneath, all the way down. Click a position to drill into that leg. Open slots are where the next placements land.</p></div><button id="dTreeToggle" class="btn btn-secondary btn-sm">List view</button></div><div id="dTreeNav" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"></div><div id="dTree"></div><div id="dGens" style="margin-top:14px"></div><p class="micro" style="margin:14px 0 0"><span class="mtp-qmark" style="position:static;display:inline-grid;vertical-align:middle">✓</span> qualified (2/2 directs) · <span class="mt-badge mt-prem">P</span> Premium · <span class="mt-badge">S</span> Standard · ⬇ everyone below that position (all generations) and the POL they've earned · <span style="color:var(--teal)">↧ spillover</span> = placed there by upline activity; only members who join with a position's own ID count toward its 2/2</p><div id="dSpillNote" class="hidden"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px;border-color:rgba(123,224,161,.4)"><h2 style="margin:0 0 4px">Your pipeline</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Money forming below you. Each generation in your leg pays your position at exactly one level — when a member's level catches up to their depth, their <em>next</em> upgrade comes to you.</p><div id="dPipeline"></div></div>
|
||||
<div id="dCoachCard" class="table-card" style="margin-bottom:18px;display:none;border-color:rgba(240,197,109,.35)"><h2 style="margin:0 0 4px">Coach your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">The fastest way to grow your own income is helping the people below you take their next step. Here's who in <em>your</em> team could use a nudge today — updated live from the blockchain.</p><div id="dCoach"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px;border-color:rgba(120,160,255,.35)"><h2 style="margin:0 0 4px">Messages <span id="msgBell" class="hidden" style="font-size:14px;background:var(--gold);color:#132;border-radius:20px;padding:2px 10px;vertical-align:middle;font-weight:800">🔔 0</span></h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Wallet-verified messaging along your matrix lines — your upline and your team can reach you here, and you can reach them. No email needed; your wallet is your identity. <span class="micro">Messages are member-to-member; the team admin can review them for abuse.</span></p><div id="dMsg"><div class="empty">Loading…</div></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px;border-color:rgba(78,214,203,.35)"><h2 style="margin:0 0 4px">Email me my alerts</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Get an email the moment this position is <strong>paid</strong>, and when it <strong>needs an upgrade</strong> to catch incoming pay — so you never miss one. Opt in with your email; unsubscribe anytime.</p><div id="dAlerts"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Share this position</h2><div id="dShare"></div><a class="btn btn-teal btn-sm" href="/tools" style="margin-top:14px">🎬 Promo Tools — posts, swipes, video clips & banners →</a></div>
|
||||
<div class="table-card" style="margin-bottom:18px;border-color:var(--gold)"><h2 style="margin:0 0 4px">Just joined under this position?</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Welcome to the team! Enter the <strong>new member ID</strong> the RM Circle dApp gave you — we'll verify it on the blockchain and let the team know you're in.</p><form id="dJoinForm" style="display:grid;gap:8px;max-width:480px"><input name="newId" class="input" inputmode="numeric" pattern="[0-9]{1,10}" maxlength="10" placeholder="Your NEW RM Circle ID (numbers only)" required><input name="memberName" class="input" maxlength="60" placeholder="Your name or Telegram @handle" required><button class="btn btn-primary">Submit My ID →</button></form><div id="dJoinMsg" style="margin-top:10px;font-size:14px"></div></div>
|
||||
@@ -30,4 +31,4 @@
|
||||
</section>
|
||||
</main>
|
||||
<footer class="wrap disclaimer">All figures are read live from the RM Circle smart contract on Polygon and are historical facts, not a promise of future results. Participation involves cryptocurrency and smart-contract risk. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a></div></footer>
|
||||
<script src="/track.js"></script><script src="/qrlib.js"></script><script src="/my.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script></body></html>
|
||||
<script src="/track.js"></script><script src="/qrlib.js"></script><script src="/rmc-wallet.js"></script><script src="/my.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script></body></html>
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
renderGens(d);
|
||||
renderPipeline(d);
|
||||
renderCoach(d);
|
||||
renderMessages(d);
|
||||
renderAlerts(d);
|
||||
renderShare(d);
|
||||
document.getElementById('dLineage').innerHTML=d.uplineChain&&d.uplineChain.length
|
||||
@@ -158,6 +159,69 @@
|
||||
}).join('');
|
||||
el.innerHTML=`<div style="border-top:1px solid var(--line);padding-top:12px"><small style="text-transform:uppercase;letter-spacing:.08em;color:var(--muted);font-size:11px">Team depth — members per generation</small><div style="margin-top:8px">${rows}</div><p class="micro" style="margin:8px 0 0">Full generations duplicate: each one can hold twice the last. A generation pays this position at exactly one level — stay qualified and at that level to catch it.</p></div>`;
|
||||
}
|
||||
// Wallet-verified messaging: sign-in = one free personal_sign; identity is
|
||||
// the wallet that owns a position; permissions follow the matrix lines.
|
||||
function renderMessages(d){
|
||||
const el=document.getElementById('dMsg'),bell=document.getElementById('msgBell');
|
||||
if(!el)return;
|
||||
fetch('/api/public/msg-unread?id='+d.id).then(r=>r.json()).then(u=>{
|
||||
if(bell&&u&&u.count>0){bell.textContent='🔔 '+u.count+' new';bell.classList.remove('hidden');}
|
||||
}).catch(()=>{});
|
||||
loadMsgUI(d);
|
||||
}
|
||||
async function loadMsgUI(d){
|
||||
const el=document.getElementById('dMsg');
|
||||
let me=null;
|
||||
try{const r=await fetch('/api/public/msg-me');if(r.ok)me=await r.json();}catch(e){}
|
||||
if(!me){
|
||||
el.innerHTML='<p class="micro" style="margin:0 0 10px">Sign in once with the wallet that owns your position — one free signature; it can\'t move funds or approve anything.</p><button id="msgAuthBtn" class="btn btn-primary">🔐 Connect wallet & sign in</button><div id="msgAuthErr" class="micro" style="color:var(--danger);margin-top:8px"></div>';
|
||||
const b=document.getElementById('msgAuthBtn');if(b)b.addEventListener('click',function(){msgAuth(d);});
|
||||
return;
|
||||
}
|
||||
let data;
|
||||
try{data=await(await fetch('/api/public/msg-inbox')).json();}catch(e){el.innerHTML='<div class="empty">Could not load messages — refresh to retry.</div>';return;}
|
||||
const mine=Number(me.id)===Number(d.id);
|
||||
const banner=mine?'':`<div class="callout" style="margin-bottom:10px">You're signed in as <strong>#${me.id}</strong> — this inbox is yours. (You're viewing #${d.id}'s page; the "to" box is pre-filled for them.)</div>`;
|
||||
const rows=(data.inbox||[]).map(m=>`<div class="pp-row" style="padding:9px 12px${m.read?'':';border-color:rgba(240,197,109,.55)'}"><div class="pp-icon">${m.org?'📣':'✉️'}</div><div class="pp-body"><strong>From #${m.fromId}</strong> <span class="pp-meta" style="display:inline">· ${new Date(m.ts).toLocaleString()}${m.org?' · team broadcast':''}${m.read?'':' · <strong style="color:var(--gold)">NEW</strong>'}</span><div style="white-space:pre-wrap;margin-top:4px">${esc(m.body)}</div></div></div>`).join('')||'<div class="empty">No messages yet.</div>';
|
||||
const sent=(data.sent||[]).slice(0,3).map(m=>`<div class="micro" style="margin:3px 0">→ ${m.org?'whole team':'#'+m.toId} · ${new Date(m.ts).toLocaleString()}: ${esc(m.body.slice(0,90))}${m.body.length>90?'…':''}</div>`).join('');
|
||||
el.innerHTML=banner+rows+
|
||||
(sent?`<div style="margin-top:10px"><span class="micro" style="text-transform:uppercase;letter-spacing:.08em">Recently sent</span>${sent}</div>`:'')+
|
||||
`<div style="border-top:1px solid var(--line);margin-top:12px;padding-top:12px"><div style="font-weight:800;margin-bottom:6px">Send a message</div>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:8px">
|
||||
<input id="msgTo" inputmode="numeric" placeholder="Member #" value="${mine?'':esc(String(d.id))}" style="max-width:110px;padding:9px 12px;border:1px solid var(--line);border-radius:10px;background:#08192880;color:var(--text);font-size:14px">
|
||||
<label class="micro" style="display:flex;gap:6px;align-items:center;cursor:pointer"><input type="checkbox" id="msgOrg"> send to my whole team instead</label>
|
||||
</div>
|
||||
<textarea id="msgBody" maxlength="1500" rows="3" placeholder="Plain text, up to 1500 characters. You can message your team and your upline." style="width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:#08192880;color:var(--text);font-size:14px"></textarea>
|
||||
<div style="display:flex;gap:10px;align-items:center;margin-top:8px"><button id="msgSendBtn" class="btn btn-primary">Send →</button><span id="msgStatus" class="micro"></span></div></div>`;
|
||||
const unreadIds=(data.inbox||[]).filter(m=>!m.read).map(m=>m.mid);
|
||||
if(unreadIds.length)fetch('/api/public/msg-read',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mids:unreadIds})}).catch(()=>{});
|
||||
const sb=document.getElementById('msgSendBtn');
|
||||
if(sb)sb.addEventListener('click',async function(){
|
||||
const st=document.getElementById('msgStatus');st.textContent='Sending…';sb.disabled=true;
|
||||
try{
|
||||
const payload={org:document.getElementById('msgOrg').checked,toId:(document.getElementById('msgTo').value||'').trim(),body:document.getElementById('msgBody').value};
|
||||
const r=await(await fetch('/api/public/msg-send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})).json();
|
||||
if(r.error){st.textContent=r.error;sb.disabled=false;return;}
|
||||
st.textContent='Sent ✓';setTimeout(function(){loadMsgUI(d);},700);
|
||||
}catch(e){st.textContent='Send failed — try again.';sb.disabled=false;}
|
||||
});
|
||||
}
|
||||
async function msgAuth(d){
|
||||
const err=document.getElementById('msgAuthErr');
|
||||
try{
|
||||
const eth=await window.RMCWallet.pick();
|
||||
if(!eth){err.textContent='No wallet found in this browser. On a phone, open this page inside your wallet app\'s browser (MetaMask or Trust).';return;}
|
||||
const accs=await eth.request({method:'eth_requestAccounts'});const account=accs[0];
|
||||
const ch=await(await fetch('/api/public/msg-challenge',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({address:account})})).json();
|
||||
if(!ch.message)throw new Error(ch.error||'Could not start sign-in.');
|
||||
let hex='0x';for(const b of new TextEncoder().encode(ch.message))hex+=b.toString(16).padStart(2,'0');
|
||||
const sig=await eth.request({method:'personal_sign',params:[hex,account]});
|
||||
const v=await(await fetch('/api/public/msg-verify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({address:account,signature:sig})})).json();
|
||||
if(!v.ok)throw new Error(v.error||'Verification failed.');
|
||||
loadMsgUI(d);
|
||||
}catch(e){if(err)err.textContent=e.message||String(e);}
|
||||
}
|
||||
|
||||
// "Coach your team" — the same triage the team admin runs, scoped to THIS
|
||||
// position's leg: who below could use a nudge, and exactly what to tell them.
|
||||
function renderCoach(d){
|
||||
|
||||
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { URL } = require('url');
|
||||
const chain = require('./chain');
|
||||
const messages = require('./messages');
|
||||
const tweet = require('./tweet');
|
||||
|
||||
const PORT = Number(process.env.PORT || 3000);
|
||||
@@ -15,6 +16,7 @@ const SPONSORS_FILE = path.join(DATA_DIR, 'sponsors.json');
|
||||
const CONFIG_FILE = path.join(DATA_DIR, 'config.json');
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'changeme';
|
||||
const IS_PROD = process.env.NODE_ENV === 'production';
|
||||
messages.init({ dataDir: DATA_DIR, chain, isProd: IS_PROD });
|
||||
const SESSION_TTL = 8 * 60 * 60 * 1000;
|
||||
const LEVELS = ['Scintilla','Ascensus','Fabrica','Culmen','Apex','Fastigium','Vertex','Corona'];
|
||||
const OPENROUTER_MODEL = process.env.OPENROUTER_MODEL || 'deepseek/deepseek-v4-flash:nitro';
|
||||
@@ -45,6 +47,7 @@ FACTS:
|
||||
- RESILIENCE ("what if the creators disappear / owner loses keys / it falls apart over time"): the contract is autonomous and immutable — NO admin action, heartbeat, or living operator is required for joins, upgrades, matrix placement, or payouts; there is no pause switch and no expiry. Verified on-chain that the founder, development, and fee-receiver wallets are ordinary wallets (EOAs), NOT smart contracts — an ordinary wallet always accepts incoming POL even if its key is lost forever, so a dead or abandoned admin wallet cannot block any member payment (only the project's OWN uncollected fee would sit idle). The contract stores no balance (every payment is delivered in the same transaction). If the owner's key were lost, only the four limited admin powers freeze in place; members are unaffected. Details in section 6 of https://rmcircle.team/contract.
|
||||
- Current team sponsor: ${a ? `ID ${a.id}${c.showSponsorName && a.name ? ` (${a.name})` : ''}, ${a.directs}/2 directs` : 'shown on the start page'}. ${waiting} placement(s) waiting. Placements rotate as positions qualify — always verify on https://rmcircle.team/start right before joining.
|
||||
- Site pages: https://rmcircle.team/ (strategy overview + roadmap + live team stats), https://rmcircle.team/start (current sponsor + join steps), https://rmcircle.team/training (6 videos — team overview, wallet setup, funding, the new connect-wallet join flow on the site, the dApp backup method, how payments work — + spillover article), https://rmcircle.team/how-pay-works (the two income streams shown as a pay-flow diagram + Premium/Standard tier comparison), https://rmcircle.team/contract (plain-language security review of the verified smart contract — code can't change, no pooled funds, locked rules, honest list of operator powers), https://rmcircle.team/my (member dashboard), https://rmcircle.team/tools (for existing team members who want to promote — share-ready promo videos, copy-paste social posts, short/long email swipes, and a downloadable banner kit in every standard size; to write promos in their own voice, mybrandedvoice.com), https://rmcircle.team/disclaimer (affiliate/earnings/risk disclosures).
|
||||
- MESSAGES (on-site, wallet-verified): every member dashboard has a Messages panel — sign in once with the wallet that owns your position (a free signature, cannot move funds), then message your upline or anyone in your own team, or broadcast to your whole team. Spam-proof by design: messaging only works along your own matrix lines, so strangers can't message you. Unread messages show as a bell on your dashboard. Members are told the team admin can review messages for abuse. No email address needed.
|
||||
- BUYING POL WITH A CARD (for people brand new to crypto): the site links to MoonPay (moonpay.com/buy/pol) on the training page, the start page, and automatically on the join page when a connected wallet's balance is short. Guidance to give: choose POL on the POLYGON network, send it to YOUR OWN wallet address, buy about entry + gas (~385 POL). MoonPay is an independent company (merchant of record) — it handles ID verification and charges its own card fee (~4.5%); this site never touches or holds anyone's money. First purchases can take a few minutes to arrive.
|
||||
- COACHING DOCTRINE (teach forward): whenever you give a member guidance about helping their team, frame it so they learn to run the same play for their own two — e.g. "do X, then show your two how to spot this on THEIR dashboard's Coach Your Team panel." The goal is never just fixing one member's next step; it is teaching people how to teach. Every member's dashboard has the same Coach Your Team panel, so the play duplicates at every depth.
|
||||
- Telegram group for live team help: ${c.telegramUrl || 'https://t.me/cryptoteambuild'}
|
||||
@@ -486,6 +489,46 @@ async function handleApi(req,res,pathname){
|
||||
}
|
||||
return json(res,200,{url:'https://www.moonpay.com/buy/pol',signed:false,pol});
|
||||
}
|
||||
// ---- wallet-verified member messaging ----
|
||||
if(req.method==='POST'&&pathname==='/api/public/msg-challenge'){
|
||||
const b=await bodyJson(req);
|
||||
if(!b||typeof b.address!=='string'||!messages.ADDR_RE.test(b.address))return json(res,400,{error:'Invalid wallet address.'});
|
||||
return json(res,200,{message:messages.makeChallenge(b.address)});
|
||||
}
|
||||
if(req.method==='POST'&&pathname==='/api/public/msg-verify'){
|
||||
const b=await bodyJson(req);
|
||||
if(!b||typeof b.address!=='string'||!messages.ADDR_RE.test(b.address)||typeof b.signature!=='string')return json(res,400,{error:'Invalid request.'});
|
||||
const r=messages.verifyChallenge(b.address,b.signature);
|
||||
if(r.error)return json(res,401,{error:r.error});
|
||||
return json(res,200,{ok:true,id:r.id},{'Set-Cookie':messages.sessionCookie(r.token)});
|
||||
}
|
||||
if(req.method==='GET'&&pathname==='/api/public/msg-me'){
|
||||
const s=messages.authFromCookie(req);
|
||||
if(!s)return json(res,401,{error:'Not signed in.'});
|
||||
return json(res,200,{id:s.id,unread:messages.unreadCount(s.id)});
|
||||
}
|
||||
if(req.method==='GET'&&pathname==='/api/public/msg-inbox'){
|
||||
const s=messages.authFromCookie(req);
|
||||
if(!s)return json(res,401,{error:'Not signed in.'});
|
||||
return json(res,200,messages.inbox(s));
|
||||
}
|
||||
if(req.method==='POST'&&pathname==='/api/public/msg-send'){
|
||||
const s=messages.authFromCookie(req);
|
||||
if(!s)return json(res,401,{error:'Not signed in.'});
|
||||
const b=await bodyJson(req);
|
||||
const r=messages.send(s,b||{});
|
||||
return json(res,r.error?400:200,r);
|
||||
}
|
||||
if(req.method==='POST'&&pathname==='/api/public/msg-read'){
|
||||
const s=messages.authFromCookie(req);
|
||||
if(!s)return json(res,401,{error:'Not signed in.'});
|
||||
const b=await bodyJson(req);
|
||||
return json(res,200,messages.markRead(s,Array.isArray(b&&b.mids)?b.mids.slice(0,100):[]));
|
||||
}
|
||||
if(req.method==='GET'&&pathname==='/api/public/msg-unread'){
|
||||
const id=Number(new URL(req.url,'http://x').searchParams.get('id')||0);
|
||||
return json(res,200,{count:messages.unreadCount(id)},{'Cache-Control':'no-store'});
|
||||
}
|
||||
if(req.method==='GET'&&pathname==='/api/public/current-sponsor'){
|
||||
const c=getConfig();
|
||||
// DORMANT until config.publicRotationMode='chain': company-wide rotation —
|
||||
@@ -529,6 +572,9 @@ async function handleApi(req,res,pathname){
|
||||
if(req.method==='GET'&&pathname==='/api/admin/matrix-tree'){
|
||||
return json(res,200,chain.getMatrixTree());
|
||||
}
|
||||
if(req.method==='GET'&&pathname==='/api/admin/messages'){
|
||||
return json(res,200,{messages:messages.adminList()});
|
||||
}
|
||||
if(req.method==='GET'&&pathname==='/api/admin/org-share'){
|
||||
const raw=new URL(req.url,'http://x').searchParams.get('root');
|
||||
const root=Number(raw||parseOwnerIds()[0]||21);
|
||||
|
||||
Vendored
+1230
File diff suppressed because it is too large
Load Diff
Vendored
+662
@@ -0,0 +1,662 @@
|
||||
/**
|
||||
* [js-sha3]{@link https://github.com/emn178/js-sha3}
|
||||
*
|
||||
* @version 0.9.3
|
||||
* @author Chen, Yi-Cyuan [emn178@gmail.com]
|
||||
* @copyright Chen, Yi-Cyuan 2015-2023
|
||||
* @license MIT
|
||||
*/
|
||||
/*jslint bitwise: true */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var INPUT_ERROR = 'input is invalid type';
|
||||
var FINALIZE_ERROR = 'finalize already called';
|
||||
var WINDOW = typeof window === 'object';
|
||||
var root = WINDOW ? window : {};
|
||||
if (root.JS_SHA3_NO_WINDOW) {
|
||||
WINDOW = false;
|
||||
}
|
||||
var WEB_WORKER = !WINDOW && typeof self === 'object';
|
||||
var NODE_JS = !root.JS_SHA3_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
|
||||
if (NODE_JS) {
|
||||
root = global;
|
||||
} else if (WEB_WORKER) {
|
||||
root = self;
|
||||
}
|
||||
var COMMON_JS = !root.JS_SHA3_NO_COMMON_JS && typeof module === 'object' && module.exports;
|
||||
var AMD = typeof define === 'function' && define.amd;
|
||||
var ARRAY_BUFFER = !root.JS_SHA3_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
|
||||
var HEX_CHARS = '0123456789abcdef'.split('');
|
||||
var SHAKE_PADDING = [31, 7936, 2031616, 520093696];
|
||||
var CSHAKE_PADDING = [4, 1024, 262144, 67108864];
|
||||
var KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
var PADDING = [6, 1536, 393216, 100663296];
|
||||
var SHIFT = [0, 8, 16, 24];
|
||||
var RC = [1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
||||
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
||||
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
||||
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
||||
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
var BITS = [224, 256, 384, 512];
|
||||
var SHAKE_BITS = [128, 256];
|
||||
var OUTPUT_TYPES = ['hex', 'buffer', 'arrayBuffer', 'array', 'digest'];
|
||||
var CSHAKE_BYTEPAD = {
|
||||
'128': 168,
|
||||
'256': 136
|
||||
};
|
||||
|
||||
|
||||
var isArray = root.JS_SHA3_NO_NODE_JS || !Array.isArray
|
||||
? function (obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
}
|
||||
: Array.isArray;
|
||||
|
||||
var isView = (ARRAY_BUFFER && (root.JS_SHA3_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView))
|
||||
? function (obj) {
|
||||
return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
|
||||
}
|
||||
: ArrayBuffer.isView;
|
||||
|
||||
// [message: string, isString: bool]
|
||||
var formatMessage = function (message) {
|
||||
var type = typeof message;
|
||||
if (type === 'string') {
|
||||
return [message, true];
|
||||
}
|
||||
if (type !== 'object' || message === null) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
|
||||
return [new Uint8Array(message), false];
|
||||
}
|
||||
if (!isArray(message) && !isView(message)) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
return [message, false];
|
||||
}
|
||||
|
||||
var empty = function (message) {
|
||||
return formatMessage(message)[0].length === 0;
|
||||
};
|
||||
|
||||
var cloneArray = function (array) {
|
||||
var newArray = [];
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
newArray[i] = array[i];
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
var createOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message) {
|
||||
return new Keccak(bits, padding, bits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createShakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits) {
|
||||
return new Keccak(bits, padding, outputBits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createCshakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits, n, s) {
|
||||
return methods['cshake' + bits].update(message, outputBits, n, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createKmacOutputMethod = function (bits, padding, outputType) {
|
||||
return function (key, message, outputBits, s) {
|
||||
return methods['kmac' + bits].update(key, message, outputBits, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createOutputMethods = function (method, createMethod, bits, padding) {
|
||||
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
|
||||
var type = OUTPUT_TYPES[i];
|
||||
method[type] = createMethod(bits, padding, type);
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
var createMethod = function (bits, padding) {
|
||||
var method = createOutputMethod(bits, padding, 'hex');
|
||||
method.create = function () {
|
||||
return new Keccak(bits, padding, bits);
|
||||
};
|
||||
method.update = function (message) {
|
||||
return method.create().update(message);
|
||||
};
|
||||
return createOutputMethods(method, createOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createShakeMethod = function (bits, padding) {
|
||||
var method = createShakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits) {
|
||||
return new Keccak(bits, padding, outputBits);
|
||||
};
|
||||
method.update = function (message, outputBits) {
|
||||
return method.create(outputBits).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createShakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createCshakeMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createCshakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits, n, s) {
|
||||
if (empty(n) && empty(s)) {
|
||||
return methods['shake' + bits].create(outputBits);
|
||||
} else {
|
||||
return new Keccak(bits, padding, outputBits).bytepad([n, s], w);
|
||||
}
|
||||
};
|
||||
method.update = function (message, outputBits, n, s) {
|
||||
return method.create(outputBits, n, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createCshakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createKmacMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createKmacOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (key, outputBits, s) {
|
||||
return new Kmac(bits, padding, outputBits).bytepad(['KMAC', s], w).bytepad([key], w);
|
||||
};
|
||||
method.update = function (key, message, outputBits, s) {
|
||||
return method.create(key, outputBits, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createKmacOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var algorithms = [
|
||||
{ name: 'keccak', padding: KECCAK_PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'sha3', padding: PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'shake', padding: SHAKE_PADDING, bits: SHAKE_BITS, createMethod: createShakeMethod },
|
||||
{ name: 'cshake', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createCshakeMethod },
|
||||
{ name: 'kmac', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createKmacMethod }
|
||||
];
|
||||
|
||||
var methods = {}, methodNames = [];
|
||||
|
||||
for (var i = 0; i < algorithms.length; ++i) {
|
||||
var algorithm = algorithms[i];
|
||||
var bits = algorithm.bits;
|
||||
for (var j = 0; j < bits.length; ++j) {
|
||||
var methodName = algorithm.name + '_' + bits[j];
|
||||
methodNames.push(methodName);
|
||||
methods[methodName] = algorithm.createMethod(bits[j], algorithm.padding);
|
||||
if (algorithm.name !== 'sha3') {
|
||||
var newMethodName = algorithm.name + bits[j];
|
||||
methodNames.push(newMethodName);
|
||||
methods[newMethodName] = methods[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Keccak(bits, padding, outputBits) {
|
||||
this.blocks = [];
|
||||
this.s = [];
|
||||
this.padding = padding;
|
||||
this.outputBits = outputBits;
|
||||
this.reset = true;
|
||||
this.finalized = false;
|
||||
this.block = 0;
|
||||
this.start = 0;
|
||||
this.blockCount = (1600 - (bits << 1)) >> 5;
|
||||
this.byteCount = this.blockCount << 2;
|
||||
this.outputBlocks = outputBits >> 5;
|
||||
this.extraBytes = (outputBits & 31) >> 3;
|
||||
|
||||
for (var i = 0; i < 50; ++i) {
|
||||
this.s[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Keccak.prototype.update = function (message) {
|
||||
if (this.finalized) {
|
||||
throw new Error(FINALIZE_ERROR);
|
||||
}
|
||||
var result = formatMessage(message);
|
||||
message = result[0];
|
||||
var isString = result[1];
|
||||
var blocks = this.blocks, byteCount = this.byteCount, length = message.length,
|
||||
blockCount = this.blockCount, index = 0, s = this.s, i, code;
|
||||
|
||||
while (index < length) {
|
||||
if (this.reset) {
|
||||
this.reset = false;
|
||||
blocks[0] = this.block;
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if (isString) {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
this.lastByteIndex = i;
|
||||
if (i >= byteCount) {
|
||||
this.start = i - byteCount;
|
||||
this.block = blocks[blockCount];
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
this.reset = true;
|
||||
} else {
|
||||
this.start = i;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.encode = function (x, right) {
|
||||
var o = x & 255, n = 1;
|
||||
var bytes = [o];
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
while (o > 0) {
|
||||
bytes.unshift(o);
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
++n;
|
||||
}
|
||||
if (right) {
|
||||
bytes.push(n);
|
||||
} else {
|
||||
bytes.unshift(n);
|
||||
}
|
||||
this.update(bytes);
|
||||
return bytes.length;
|
||||
};
|
||||
|
||||
Keccak.prototype.encodeString = function (str) {
|
||||
var result = formatMessage(str);
|
||||
str = result[0];
|
||||
var isString = result[1];
|
||||
var bytes = 0, length = str.length;
|
||||
if (isString) {
|
||||
for (var i = 0; i < str.length; ++i) {
|
||||
var code = str.charCodeAt(i);
|
||||
if (code < 0x80) {
|
||||
bytes += 1;
|
||||
} else if (code < 0x800) {
|
||||
bytes += 2;
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
bytes += 3;
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (str.charCodeAt(++i) & 0x3ff));
|
||||
bytes += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bytes = length;
|
||||
}
|
||||
bytes += this.encode(bytes * 8);
|
||||
this.update(str);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
Keccak.prototype.bytepad = function (strs, w) {
|
||||
var bytes = this.encode(w);
|
||||
for (var i = 0; i < strs.length; ++i) {
|
||||
bytes += this.encodeString(strs[i]);
|
||||
}
|
||||
var paddingBytes = (w - bytes % w) % w;
|
||||
var zeros = [];
|
||||
zeros.length = paddingBytes;
|
||||
this.update(zeros);
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.finalize = function () {
|
||||
if (this.finalized) {
|
||||
return;
|
||||
}
|
||||
this.finalized = true;
|
||||
var blocks = this.blocks, i = this.lastByteIndex, blockCount = this.blockCount, s = this.s;
|
||||
blocks[i >> 2] |= this.padding[i & 3];
|
||||
if (this.lastByteIndex === this.byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
};
|
||||
|
||||
Keccak.prototype.toString = Keccak.prototype.hex = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var hex = '', block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
||||
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
||||
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
||||
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
s = cloneArray(s);
|
||||
f(s);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F];
|
||||
if (extraBytes > 1) {
|
||||
hex += HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F];
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
hex += HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F];
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
Keccak.prototype.arrayBuffer = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var bytes = this.outputBits >> 3;
|
||||
var buffer;
|
||||
if (extraBytes) {
|
||||
buffer = new ArrayBuffer((outputBlocks + 1) << 2);
|
||||
} else {
|
||||
buffer = new ArrayBuffer(bytes);
|
||||
}
|
||||
var array = new Uint32Array(buffer);
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
array[j] = s[i];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
s = cloneArray(s);
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
array[j] = s[i];
|
||||
buffer = buffer.slice(0, bytes);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Keccak.prototype.buffer = Keccak.prototype.arrayBuffer;
|
||||
|
||||
Keccak.prototype.digest = Keccak.prototype.array = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var array = [], offset, block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
array[offset + 3] = (block >> 24) & 0xFF;
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
s = cloneArray(s);
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
if (extraBytes > 1) {
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
function Kmac(bits, padding, outputBits) {
|
||||
Keccak.call(this, bits, padding, outputBits);
|
||||
}
|
||||
|
||||
Kmac.prototype = new Keccak();
|
||||
|
||||
Kmac.prototype.finalize = function () {
|
||||
this.encode(this.outputBits, true);
|
||||
return Keccak.prototype.finalize.call(this);
|
||||
};
|
||||
|
||||
var f = function (s) {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9,
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17,
|
||||
b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33,
|
||||
b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
for (n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ ((c2 << 1) | (c3 >>> 31));
|
||||
l = c9 ^ ((c3 << 1) | (c2 >>> 31));
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ ((c4 << 1) | (c5 >>> 31));
|
||||
l = c1 ^ ((c5 << 1) | (c4 >>> 31));
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ ((c6 << 1) | (c7 >>> 31));
|
||||
l = c3 ^ ((c7 << 1) | (c6 >>> 31));
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ ((c8 << 1) | (c9 >>> 31));
|
||||
l = c5 ^ ((c9 << 1) | (c8 >>> 31));
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ ((c0 << 1) | (c1 >>> 31));
|
||||
l = c7 ^ ((c1 << 1) | (c0 >>> 31));
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = (s[11] << 4) | (s[10] >>> 28);
|
||||
b33 = (s[10] << 4) | (s[11] >>> 28);
|
||||
b14 = (s[20] << 3) | (s[21] >>> 29);
|
||||
b15 = (s[21] << 3) | (s[20] >>> 29);
|
||||
b46 = (s[31] << 9) | (s[30] >>> 23);
|
||||
b47 = (s[30] << 9) | (s[31] >>> 23);
|
||||
b28 = (s[40] << 18) | (s[41] >>> 14);
|
||||
b29 = (s[41] << 18) | (s[40] >>> 14);
|
||||
b20 = (s[2] << 1) | (s[3] >>> 31);
|
||||
b21 = (s[3] << 1) | (s[2] >>> 31);
|
||||
b2 = (s[13] << 12) | (s[12] >>> 20);
|
||||
b3 = (s[12] << 12) | (s[13] >>> 20);
|
||||
b34 = (s[22] << 10) | (s[23] >>> 22);
|
||||
b35 = (s[23] << 10) | (s[22] >>> 22);
|
||||
b16 = (s[33] << 13) | (s[32] >>> 19);
|
||||
b17 = (s[32] << 13) | (s[33] >>> 19);
|
||||
b48 = (s[42] << 2) | (s[43] >>> 30);
|
||||
b49 = (s[43] << 2) | (s[42] >>> 30);
|
||||
b40 = (s[5] << 30) | (s[4] >>> 2);
|
||||
b41 = (s[4] << 30) | (s[5] >>> 2);
|
||||
b22 = (s[14] << 6) | (s[15] >>> 26);
|
||||
b23 = (s[15] << 6) | (s[14] >>> 26);
|
||||
b4 = (s[25] << 11) | (s[24] >>> 21);
|
||||
b5 = (s[24] << 11) | (s[25] >>> 21);
|
||||
b36 = (s[34] << 15) | (s[35] >>> 17);
|
||||
b37 = (s[35] << 15) | (s[34] >>> 17);
|
||||
b18 = (s[45] << 29) | (s[44] >>> 3);
|
||||
b19 = (s[44] << 29) | (s[45] >>> 3);
|
||||
b10 = (s[6] << 28) | (s[7] >>> 4);
|
||||
b11 = (s[7] << 28) | (s[6] >>> 4);
|
||||
b42 = (s[17] << 23) | (s[16] >>> 9);
|
||||
b43 = (s[16] << 23) | (s[17] >>> 9);
|
||||
b24 = (s[26] << 25) | (s[27] >>> 7);
|
||||
b25 = (s[27] << 25) | (s[26] >>> 7);
|
||||
b6 = (s[36] << 21) | (s[37] >>> 11);
|
||||
b7 = (s[37] << 21) | (s[36] >>> 11);
|
||||
b38 = (s[47] << 24) | (s[46] >>> 8);
|
||||
b39 = (s[46] << 24) | (s[47] >>> 8);
|
||||
b30 = (s[8] << 27) | (s[9] >>> 5);
|
||||
b31 = (s[9] << 27) | (s[8] >>> 5);
|
||||
b12 = (s[18] << 20) | (s[19] >>> 12);
|
||||
b13 = (s[19] << 20) | (s[18] >>> 12);
|
||||
b44 = (s[29] << 7) | (s[28] >>> 25);
|
||||
b45 = (s[28] << 7) | (s[29] >>> 25);
|
||||
b26 = (s[38] << 8) | (s[39] >>> 24);
|
||||
b27 = (s[39] << 8) | (s[38] >>> 24);
|
||||
b8 = (s[48] << 14) | (s[49] >>> 18);
|
||||
b9 = (s[49] << 14) | (s[48] >>> 18);
|
||||
|
||||
s[0] = b0 ^ (~b2 & b4);
|
||||
s[1] = b1 ^ (~b3 & b5);
|
||||
s[10] = b10 ^ (~b12 & b14);
|
||||
s[11] = b11 ^ (~b13 & b15);
|
||||
s[20] = b20 ^ (~b22 & b24);
|
||||
s[21] = b21 ^ (~b23 & b25);
|
||||
s[30] = b30 ^ (~b32 & b34);
|
||||
s[31] = b31 ^ (~b33 & b35);
|
||||
s[40] = b40 ^ (~b42 & b44);
|
||||
s[41] = b41 ^ (~b43 & b45);
|
||||
s[2] = b2 ^ (~b4 & b6);
|
||||
s[3] = b3 ^ (~b5 & b7);
|
||||
s[12] = b12 ^ (~b14 & b16);
|
||||
s[13] = b13 ^ (~b15 & b17);
|
||||
s[22] = b22 ^ (~b24 & b26);
|
||||
s[23] = b23 ^ (~b25 & b27);
|
||||
s[32] = b32 ^ (~b34 & b36);
|
||||
s[33] = b33 ^ (~b35 & b37);
|
||||
s[42] = b42 ^ (~b44 & b46);
|
||||
s[43] = b43 ^ (~b45 & b47);
|
||||
s[4] = b4 ^ (~b6 & b8);
|
||||
s[5] = b5 ^ (~b7 & b9);
|
||||
s[14] = b14 ^ (~b16 & b18);
|
||||
s[15] = b15 ^ (~b17 & b19);
|
||||
s[24] = b24 ^ (~b26 & b28);
|
||||
s[25] = b25 ^ (~b27 & b29);
|
||||
s[34] = b34 ^ (~b36 & b38);
|
||||
s[35] = b35 ^ (~b37 & b39);
|
||||
s[44] = b44 ^ (~b46 & b48);
|
||||
s[45] = b45 ^ (~b47 & b49);
|
||||
s[6] = b6 ^ (~b8 & b0);
|
||||
s[7] = b7 ^ (~b9 & b1);
|
||||
s[16] = b16 ^ (~b18 & b10);
|
||||
s[17] = b17 ^ (~b19 & b11);
|
||||
s[26] = b26 ^ (~b28 & b20);
|
||||
s[27] = b27 ^ (~b29 & b21);
|
||||
s[36] = b36 ^ (~b38 & b30);
|
||||
s[37] = b37 ^ (~b39 & b31);
|
||||
s[46] = b46 ^ (~b48 & b40);
|
||||
s[47] = b47 ^ (~b49 & b41);
|
||||
s[8] = b8 ^ (~b0 & b2);
|
||||
s[9] = b9 ^ (~b1 & b3);
|
||||
s[18] = b18 ^ (~b10 & b12);
|
||||
s[19] = b19 ^ (~b11 & b13);
|
||||
s[28] = b28 ^ (~b20 & b22);
|
||||
s[29] = b29 ^ (~b21 & b23);
|
||||
s[38] = b38 ^ (~b30 & b32);
|
||||
s[39] = b39 ^ (~b31 & b33);
|
||||
s[48] = b48 ^ (~b40 & b42);
|
||||
s[49] = b49 ^ (~b41 & b43);
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
};
|
||||
|
||||
if (COMMON_JS) {
|
||||
module.exports = methods;
|
||||
} else {
|
||||
for (i = 0; i < methodNames.length; ++i) {
|
||||
root[methodNames[i]] = methods[methodNames[i]];
|
||||
}
|
||||
if (AMD) {
|
||||
define(function () {
|
||||
return methods;
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user