gimmickCellar

HashBrown 2

Mar 6th, 2026 (edited)
246
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.54 KB | None | 0 0
  1. // ==UserScript==
  2. // @name HashBrown 2
  3. // @namespace http://tampermonkey.net/
  4. // @version 2026-05-28
  5. // @description Cryptographically secure identification
  6. // @author gimmickCellar
  7. // @match https://*.ourworldoftext.com/*
  8. // @match https://twot.fbin.duckdns.org/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=ourworldoftext.com
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const P256 = {
  17. p: 2n**256n - 2n**224n + 2n**192n + 2n**96n - 1n,
  18. a: -3n,
  19. b: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn,
  20. n: 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n,
  21. gx: 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296n,
  22. gy: 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5n
  23. };
  24.  
  25. const mod = (a, b) => { const r = a % b; return r < 0n ? r + b : r; };
  26.  
  27. const modInv = (a, m) => {
  28. a = mod(a, m);
  29. if (a === 0n) return 0n;
  30. let [t, newT, r, newR] = [0n, 1n, m, a];
  31. while (newR !== 0n) {
  32. let q = r / newR;
  33. [t, newT] = [newT, t - q * newT];
  34. [r, newR] = [newR, r - q * newR];
  35. }
  36. return mod(t, m);
  37. };
  38.  
  39. const pointAdd = (P, Q) => {
  40. if (!P) return Q; if (!Q) return P;
  41. let lam;
  42. if (P.x === Q.x && P.y === Q.y) {
  43. lam = mod((3n * P.x**2n + P256.a) * modInv(2n * P.y, P256.p), P256.p);
  44. } else {
  45. if (P.x === Q.x) return null;
  46. lam = mod((Q.y - P.y) * modInv(Q.x - P.x, P256.p), P256.p);
  47. }
  48. const x = mod(lam**2n - P.x - Q.x, P256.p);
  49. const y = mod(lam * (P.x - x) - P.y, P256.p);
  50. return { x, y };
  51. };
  52.  
  53. const pointMul = (P, k) => {
  54. let res = null, base = P;
  55. while (k > 0n) {
  56. if (k & 1n) res = pointAdd(res, base);
  57. base = pointAdd(base, base);
  58. k >>= 1n;
  59. }
  60. return res;
  61. };
  62.  
  63. function sha256(s) {
  64. const chrsz = 8;
  65. function safe_add(x, y) {
  66. const lsw = (x & 0xFFFF) + (y & 0xFFFF);
  67. const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  68. return (msw << 16) | (lsw & 0xFFFF);
  69. }
  70. const S = (X, n) => (X >>> n) | (X << (32 - n));
  71. const R = (X, n) => (X >>> n);
  72. const Ch = (x, y, z) => ((x & y) ^ ((~x) & z));
  73. const Maj = (x, y, z) => ((x & y) ^ (x & z) ^ (y & z));
  74. const Sigma0256 = (x) => (S(x, 2) ^ S(x, 13) ^ S(x, 22));
  75. const Sigma1256 = (x) => (S(x, 6) ^ S(x, 11) ^ S(x, 25));
  76. const Gamma0256 = (x) => (S(x, 7) ^ S(x, 18) ^ R(x, 3));
  77. const Gamma1256 = (x) => (S(x, 17) ^ S(x, 19) ^ R(x, 10));
  78. const K = [0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2];
  79. const HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
  80. const utf8 = unescape(encodeURIComponent(s));
  81. const m = [];
  82. for (let i = 0; i < utf8.length * chrsz; i += chrsz) m[i >> 5] |= (utf8.charCodeAt(i / chrsz) & 0xFF) << (24 - i % 32);
  83. const l = utf8.length * chrsz;
  84. const W = new Array(64);
  85. m[l >> 5] |= 0x80 << (24 - l % 32);
  86. m[((l + 64 >> 9) << 4) + 15] = l;
  87. for (let i = 0; i < m.length; i += 16) {
  88. let [a, b, c, d, e, f, g, h] = HASH;
  89. for (let j = 0; j < 64; j++) {
  90. if (j < 16) W[j] = m[i + j] || 0;
  91. else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
  92. let T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
  93. let T2 = safe_add(Sigma0256(a), Maj(a, b, c));
  94. h = g; g = f; f = e; e = safe_add(d, T1); d = c; c = b; b = a; a = safe_add(T1, T2);
  95. }
  96. HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]); HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]);
  97. HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]); HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
  98. }
  99. let hex = "";
  100. for (let i = 0; i < HASH.length * 4; i++) {
  101. hex += ((HASH[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF).toString(16) + ((HASH[i >> 2] >> ((3 - i % 4) * 8)) & 0xF).toString(16);
  102. }
  103. return BigInt('0x' + hex);
  104. }
  105.  
  106. const bufferToBase64 = (buf) => { let bin = ''; for (let i = 0; i < buf.byteLength; i++) bin += String.fromCharCode(buf[i]); return btoa(bin); };
  107. const base64ToBuffer = (b64) => { const bin = atob(b64); const buf = new Uint8Array(bin.length); for(let i=0; i<bin.length; i++) buf[i] = bin.charCodeAt(i); return buf; };
  108. const bigToBuf = (bn) => { let hex = bn.toString(16).padStart(64, '0'); const arr = new Uint8Array(32); for (let i = 0; i < 32; i++) arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); return arr; };
  109. const bufToHex = (buf) => { return Array.from(buf).map(b => b.toString(16).padStart(2, '0')).join(''); };
  110.  
  111. function verifyMessage(msg, sigBase64, pubBase64) {
  112. try {
  113. const z = sha256(msg);
  114. const sigBuf = base64ToBuffer(sigBase64);
  115. const pubBuf = base64ToBuffer(pubBase64);
  116. if (sigBuf.length !== 64 || pubBuf.length !== 65 || pubBuf[0] !== 4) return false;
  117. const r = BigInt('0x' + bufToHex(sigBuf.subarray(0, 32)));
  118. const s = BigInt('0x' + bufToHex(sigBuf.subarray(32, 64)));
  119. if (r < 1n || r >= P256.n || s < 1n || s >= P256.n) return false;
  120. const px = BigInt('0x' + bufToHex(pubBuf.subarray(1, 33)));
  121. const py = BigInt('0x' + bufToHex(pubBuf.subarray(33, 65)));
  122. const w = modInv(s, P256.n);
  123. const u1 = mod(z * w, P256.n);
  124. const u2 = mod(r * w, P256.n);
  125. const pt1 = pointMul({ x: P256.gx, y: P256.gy }, u1);
  126. const pt2 = pointMul({ x: px, y: py }, u2);
  127. const pt = pointAdd(pt1, pt2);
  128. return pt && mod(pt.x, P256.n) === r;
  129. } catch(e) { return false; }
  130. }
  131.  
  132. let privKey, pubKeyBase64, activePhraseData = null;
  133. let linkType = localStorage.getItem("HashBrown_LinkType") || "note";
  134. let verifierUrl = localStorage.getItem("HashBrown_VerifierURL") || "https://gimmickcellar.nekoweb.org/verifyhash.html";
  135. let signingEnabled = localStorage.getItem("HashBrown_Signing") !== "false";
  136. let autoVerifyEnabled = localStorage.getItem("HashBrown_AutoVerify") !== "false";
  137.  
  138. function initCrypto() {
  139. let stored = localStorage.getItem('HashBrown_PrivV4');
  140. if (!stored) {
  141. const tmp = new Uint8Array(32); crypto.getRandomValues(tmp);
  142. stored = Array.from(tmp).map(b => b.toString(16).padStart(2, '0')).join('');
  143. localStorage.setItem('HashBrown_PrivV4', stored);
  144. }
  145. privKey = BigInt('0x' + stored);
  146. const pt = pointMul({ x: P256.gx, y: P256.gy }, privKey);
  147. const rawPub = new Uint8Array(65);
  148. rawPub[0] = 4;
  149. rawPub.set(bigToBuf(pt.x), 1);
  150. rawPub.set(bigToBuf(pt.y), 33);
  151. pubKeyBase64 = bufferToBase64(rawPub);
  152. }
  153.  
  154. function signMessage(msg) {
  155. const z = sha256(msg);
  156. let r = 0n, s = 0n;
  157. while (r === 0n || s === 0n) {
  158. const kArr = new Uint8Array(32); crypto.getRandomValues(kArr);
  159. const kStr = Array.from(kArr).map(b => b.toString(16).padStart(2, '0')).join('');
  160. const k = mod(BigInt('0x' + kStr), P256.n);
  161. if (k === 0n) continue;
  162. const kp = pointMul({ x: P256.gx, y: P256.gy }, k);
  163. r = mod(kp.x, P256.n);
  164. s = mod(modInv(k, P256.n) * (z + r * privKey), P256.n);
  165. }
  166. const sigArr = new Uint8Array(64);
  167. sigArr.set(bigToBuf(r), 0);
  168. sigArr.set(bigToBuf(s), 32);
  169. return bufferToBase64(sigArr);
  170. }
  171.  
  172. function buildUI() {
  173. const gui = document.getElementById("west_gui");
  174. if (!gui) return;
  175. const hb = document.createElement("div");
  176. hb.style = "background:#E5E5FF;padding:4px;margin-bottom:3px;font-family:Verdana;font-size:12px;";
  177. hb.innerHTML = `
  178. <center><b>HashBrown 2</b></center>
  179. <label style="display:block;margin-top:4px;"><input type="checkbox" id="hb_opt_signing" ${signingEnabled?"checked":""}> Signing</label>
  180. <label style="display:block;margin-bottom:4px;"><input type="checkbox" id="hb_opt_verify" ${autoVerifyEnabled?"checked":""}> Auto-Verify</label>
  181. <label><input type="radio" name="hbt" id="hbo_n"> Note</label>
  182. <label><input type="radio" name="hbt" id="hbo_j"> JS</label>
  183. <label><input type="radio" name="hbt" id="hbo_u"> URL</label>
  184. <input type="text" id="hb_url" placeholder="Verifier URL" style="width:100%;font-size:10px;display:none;margin-top:2px;">
  185. <hr>
  186. <input type="text" id="hb_in" placeholder="Phrase..." style="width:100%;font-size:11px;">
  187. <button id="hb_btn" style="width:100%;margin-top:2px;cursor:pointer;">Sign & Write</button>
  188. `;
  189. gui.appendChild(hb);
  190. const uBox = document.getElementById("hb_url");
  191. uBox.value = verifierUrl;
  192. const setMode = (m) => { linkType = m; localStorage.setItem("HashBrown_LinkType", m); uBox.style.display = (m==='url'?'block':'none'); };
  193. if (linkType === "js") document.getElementById("hbo_j").checked = true;
  194. else if (linkType === "url") { document.getElementById("hbo_u").checked = true; uBox.style.display="block"; }
  195. else document.getElementById("hbo_n").checked = true;
  196. document.getElementById("hbo_n").onchange = () => setMode("note");
  197. document.getElementById("hbo_j").onchange = () => setMode("js");
  198. document.getElementById("hbo_u").onchange = () => setMode("url");
  199. uBox.oninput = (e) => { verifierUrl = e.target.value; localStorage.setItem("HashBrown_VerifierURL", verifierUrl); };
  200. document.getElementById("hb_opt_signing").onchange = (e) => { signingEnabled = e.target.checked; localStorage.setItem("HashBrown_Signing", signingEnabled); };
  201. document.getElementById("hb_opt_verify").onchange = (e) => { autoVerifyEnabled = e.target.checked; localStorage.setItem("HashBrown_AutoVerify", autoVerifyEnabled); if (autoVerifyEnabled) scanAllTiles(); };
  202. document.getElementById("hb_btn").onclick = () => {
  203. const val = document.getElementById("hb_in").value;
  204. if (!val || !window.cursorCoords || !signingEnabled) return;
  205. const startX = (cursorCoords[0] * 16) + cursorCoords[2];
  206. const startY = (cursorCoords[1] * 8) + cursorCoords[3];
  207. const sig = signMessage(`${state.worldModel.name || "Main"},${startX},${startY},${val},${state.userModel.username || "Anonymous"}`);
  208. activePhraseData = { text: val, startX, startY, sig };
  209. for (let i = 0; i < val.length; i++) OWOT.typeChar(val[i], false);
  210. activePhraseData = null;
  211. document.getElementById("hb_in").value = "";
  212. };
  213. }
  214.  
  215. initCrypto();
  216. if (document.readyState === "complete") buildUI(); else window.addEventListener("load", buildUI);
  217.  
  218. const cache = {};
  219. OWOT.on("write", (data) => {
  220. if (!signingEnabled || data.char === "\x08" || data.char === " " || data.char === "\xa0") return;
  221. const id = nextObjId - 1;
  222. const user = state.userModel.username || "Anonymous";
  223. const world = state.worldModel.name || "Main";
  224. if (activePhraseData) {
  225. cache[id] = { type: "phrase", tx: data.tileX, ty: data.tileY, cx: data.charX, cy: data.charY, user, ...activePhraseData };
  226. } else {
  227. const x = (data.tileX * 16) + data.charX, y = (data.tileY * 8) + data.charY;
  228. let b = data.bold != null ? data.bold : (document.getElementById("text_deco_b")?.style.backgroundColor !== "");
  229. let i = data.italic != null ? data.italic : (document.getElementById("text_deco_i")?.style.backgroundColor !== "");
  230. let u = data.underline != null ? data.underline : (document.getElementById("text_deco_u")?.style.backgroundColor !== "");
  231. let s = data.strikethrough != null ? data.strikethrough : (document.getElementById("text_deco_s")?.style.backgroundColor !== "");
  232. let c = data.char.codePointAt(0) || 0;
  233. let code = data.char.charCodeAt(data.char.length - 1) - 0x20F0;
  234. if (code > 0 && code <= 16) {
  235. b = b || ((code >> 3) & 1); i = i || ((code >> 2) & 1); u = u || ((code >> 1) & 1); s = s || (code & 1);
  236. }
  237. if (c === 32) { b = false; i = false; }
  238. const d = (b ? 8 : 0) | (i ? 4 : 0) | (u ? 2 : 0) | (s ? 1 : 0);
  239. cache[id] = { type: "single", tx: data.tileX, ty: data.tileY, cx: data.charX, cy: data.charY, user, sig: signMessage(`${world},${x},${y},${c},${d},${user}`), c, d };
  240. }
  241. });
  242.  
  243. OWOT.on("writeResponse", (data) => {
  244. if (!data.accepted) return;
  245. data.accepted.forEach(id => {
  246. const info = cache[id];
  247. if (!info) return;
  248. const world = state.worldModel.name || "Main";
  249. const pub = encodeURIComponent(pubKeyBase64).replace(/\+/g, '%2B');
  250. const sig = encodeURIComponent(info.sig).replace(/\+/g, '%2B');
  251. let link = "";
  252. if (info.type === "phrase") {
  253. if (linkType === "js") link = `javascript:/*\nUser: ${info.user}\nPhrase: ${info.text}\nStartX: ${info.startX}\nStartY: ${info.startY}\nSig: ${info.sig}\nPubKey: ${pubKeyBase64}\n*/`;
  254. else if (linkType === "url") link = verifierUrl + `?w=${encodeURIComponent(world)}&x=${info.startX}&y=${info.startY}&t=${encodeURIComponent(info.text)}&u=${encodeURIComponent(info.user)}&sig=${sig}&pub=${pub}`;
  255. else link = `note: User: ${info.user} | Phrase: ${info.text} | Sig: ${info.sig}`;
  256. } else {
  257. const gx = (info.tx*16)+info.cx, gy = (info.ty*8)+info.cy;
  258. if (linkType === "js") link = `javascript:/*\nUser: ${info.user}\nSig: ${info.sig}\nPubKey: ${pubKeyBase64}\n*/`;
  259. else if (linkType === "url") link = verifierUrl + `?w=${encodeURIComponent(world)}&x=${gx}&y=${gy}&c=${info.c}&d=${info.d}&u=${encodeURIComponent(info.user)}&sig=${sig}&pub=${pub}`;
  260. else link = `note: User: ${info.user} | Sig: ${info.sig} | PubKey: ${pubKeyBase64}`;
  261. }
  262. network.link({ tileX: info.tx, tileY: info.ty, charX: info.cx, charY: info.cy }, "url", { url: link });
  263. delete cache[id];
  264. });
  265. });
  266.  
  267. const verifyQueue = [];
  268. let isBusy = false;
  269.  
  270. function parseHash(url) {
  271. try {
  272. if (url.includes("sig=") && url.includes("pub=")) {
  273. const p = new URLSearchParams(url.split('?')[1]);
  274. return { sig: p.get("sig"), pub: p.get("pub"), w: p.get("w"), x: p.get("x"), y: p.get("y"), c: p.get("c"), d: p.get("d"), u: p.get("u"), t: p.get("t"), type: p.get("t")?"phrase":"single" };
  275. } else if (url.startsWith("javascript:")) {
  276. let dUrl = url;
  277. try { dUrl = decodeURIComponent(url); } catch(e) {}
  278. const sig = dUrl.match(/Sig:\s*([A-Za-z0-9+/=]+)/), pub = dUrl.match(/PubKey:\s*([A-Za-z0-9+/=]+)/);
  279. if (!sig || !pub) return null;
  280. return {
  281. type: dUrl.includes("Phrase:") ? "phrase" : "single",
  282. u: (dUrl.match(/User:\s*([^\n\r]+)/)||[,""])[1].trim(),
  283. t: (dUrl.match(/Phrase:\s*([^\n\r]+)/)||[,""])[1]?.trim() || null,
  284. x: (dUrl.match(/StartX:\s*(-?\d+)/)||[,""])[1] || null,
  285. y: (dUrl.match(/StartY:\s*(-?\d+)/)||[,""])[1] || null,
  286. sig: sig[1], pub: pub[1]
  287. };
  288. }
  289. } catch(e) {} return null;
  290. }
  291.  
  292. async function processVerify(item) {
  293. const { tx, ty, cx, cy, link } = item;
  294. const p = parseHash(link.url);
  295. if (!p) return;
  296. const tile = OWOT.tile.get(tx, ty);
  297. if (!tile) return;
  298. const world = state.worldModel.name || "Main", gridX = (tx * 16) + cx, gridY = (ty * 8) + cy, char = tile.content[cy * 16 + cx];
  299. let msg = "", contextMatch = false;
  300. if (p.type === "phrase") {
  301. msg = `${p.w||world},${p.x},${p.y},${p.t},${p.u}`;
  302. contextMatch = (p.w||world) === world;
  303. } else {
  304. const gridC = char.codePointAt(0) || 0;
  305. let code = char.charCodeAt(char.length - 1) - 0x20F0;
  306. let gridD = (code > 0 && code <= 16) ? code : 0;
  307. if (gridC === 32) {
  308. let u = (gridD >> 1) & 1;
  309. let s = gridD & 1;
  310. gridD = (u ? 2 : 0) | (s ? 1 : 0);
  311. }
  312. const linkX = p.x != null ? parseInt(p.x) : gridX;
  313. const linkY = p.y != null ? parseInt(p.y) : gridY;
  314. const linkC = p.c != null ? parseInt(p.c) : gridC;
  315. const linkD = p.d != null ? parseInt(p.d) : gridD;
  316. msg = `${p.w||world},${linkX},${linkY},${linkC},${linkD},${p.u}`;
  317. contextMatch = (gridX === linkX && gridY === linkY && gridC === linkC && gridD === linkD && (p.w||world) === world);
  318. }
  319. const validMath = verifyMessage(msg, p.sig, p.pub);
  320. const finalColor = (validMath && contextMatch) ? 0xA0FF90 : 0xFF9090;
  321. if (!tile.properties.bgcolor) tile.properties.bgcolor = new Array(128).fill(-1);
  322. tile.properties.bgcolor[cy * 16 + cx] = finalColor;
  323. OWOT.setTileRedraw(tx, ty, true);
  324. }
  325.  
  326. async function runQueue() {
  327. if (isBusy) return; isBusy = true;
  328. while (verifyQueue.length > 0 && autoVerifyEnabled) {
  329. await processVerify(verifyQueue.shift());
  330. await new Promise(r => setTimeout(r, 1));
  331. }
  332. isBusy = false;
  333. }
  334.  
  335. function scanTile(tile, tx, ty) {
  336. if (!tile?.properties?.cell_props) return;
  337. const props = tile.properties.cell_props;
  338. for (let y in props) for (let x in props[y]) {
  339. const l = props[y][x].link;
  340. if (l?.url && (l.url.includes("sig=") || l.url.includes("Sig:"))) verifyQueue.push({ tx: parseInt(tx), ty: parseInt(ty), cx: parseInt(x), cy: parseInt(y), link: l });
  341. }
  342. runQueue();
  343. }
  344.  
  345. const scanAllTiles = () => { for (let k in OWOT.tiles) { const [y, x] = k.split(","); scanTile(OWOT.tiles[k], x, y); } };
  346. OWOT.on("afterFetch", (d) => { if (autoVerifyEnabled) for (let k in d.tiles) { const [y, x] = k.split(","); scanTile(d.tiles[k], x, y); } });
  347. OWOT.on("afterTileUpdate", (d) => { if (autoVerifyEnabled) for (let k in d.tiles) { const [y, x] = k.split(","); scanTile(OWOT.tiles[k], x, y); } });
  348. })();
Add Comment
Please, Sign In to add comment