1/** 2 * CryptoUtils - Standalone cryptocurrency utilities library 3 * 4 * STANDALONE USAGE (outside Bitrequest): 5 * ---------------------------------------- 6 * <script src="assets/js/lib/sjcl.js"></script> 7 * <script src="assets/js/lib/crypto_utils.js"></script> 8 * <script> 9 * const bytes = CryptoUtils.hex_to_bytes("crypto"); 10 * const addr = CryptoUtils.pub_to_address_bech32("bc", pubkey); 11 * </script> 12 * 13 * FEATURES: 14 * - Base58 / Base58Check encoding 15 * - Bech32 / Bech32m encoding 16 * - Secp256k1 elliptic curve operations 17 * - SHA256, RIPEMD160, Hash160, Keccak-256 18 * - Bitcoin/Litecoin/Ethereum address generation 19 * - Bitcoin Cash CashAddr support 20 * - BIP39 mnemonic utilities 21 * - AES encryption/decryption 22 * - LNURL decoding 23 * 24 * @version 1.1.0 25 * @license AGPL-3.0 26 * @see https://github.com/bitrequest/bitrequest.github.io 27 * secp256k1 implementation based onhttps://github.com/paulmillr/noble-secp256k1 28 */ 29 30// ============================================ 31// CONSTANTS 32// ============================================ 33 34const crypto = window.crypto, 35 b58ab = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 36 b32ab = "qpzry9x8gf2tvdw0s3jn54khce6mua7l", 37 generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3], 38 bytestring = "0000000000000000000000000000000000000000000000000000000000000000", 39 utf8_encoder = new TextEncoder(), 40 utf8_decoder = new TextDecoder("utf-8", { 41 "fatal": true 42 }), 43 BECH32_CONST = 1, 44 BECH32M_CONST = 0x2bc830a3, 45 NIMIQ_ALPHABET = "0123456789ABCDEFGHJKLMNPQRSTUVXY", 46 NANO_ALPHABET = "13456789abcdefghijkmnopqrstuwxyz"; 47 48// Secp256k1 curve parameters 49const secp = {}, 50 CURVE = { 51 "a": 0n, 52 "b": 7n, 53 "P": (2n ** 256n) - (2n ** 32n) - 977n, 54 "n": (2n ** 256n) - 432420386565659656852420866394968145599n, 55 "Gx": 55066263022277343669578718895168534326250603453777594175500187360389116729240n, 56 "Gy": 32670510020758816978083085130507043184471273380659243275938904335757337482424n 57 }; 58secp.CURVE = CURVE; 59 60// ============================================ 61// TEST CONSTANTS 62// bip39 (All addresses / xpubs in this app are test addresses derived from the following testphrase, taken from https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch05.asciidoc) 63// "army van defense carry jealous true garbage claim echo media make crunch" 64// ============================================ 65 66const crypto_utils_const = { 67 "version": "1.1.0", 68 "test_privkey": "922c2cc579600419c8cde59ca8fcb03518ddbb5c38ed1bfbd150cb100e1f5430", 69 "test_pubkey": "036740c4f55d64fb6c9bc412084638b80062cee07f6c84b205671584e82a7c96b7", 70 "test_pubkey_bech32": "03bb4a626f63436a64d7cf1e441713cc964c0d53289a5b17acb1b9c262be57cb17", 71 "test_address": "1HQ3rb7nyLPrjnuW85MUknPekwkn7poAUm", 72 "test_address_bech32": "bc1qg0azlj4w2lrq8jssrrz6eprt2fe7f7edm4vpd5", 73 "test_pubkey_eth": "03c026c4b041059c84a187252682b6f80cbbe64eb81497111ab6914b050a8936fd", 74 "test_address_eth": "0x2161DedC3Be05B7Bb5aa16154BcbD254E9e9eb68", 75 "test_bch_legacy_address": "1AVPurYZinnctgGPiXziwU6PuyZKX5rYZU", 76 "test_bch_cashaddr": "qp5p0eur784pk8wxy2kzlz3ctnq5whfnuqqpp78u22", 77 "test_pubkey_kaspa": "035bed9ca853f2539607e6688059d6616c5acd86cd34ac987927be3c4b62065135", 78 "test_address_kaspa": "kaspa:qpd7m89g20e989s8ue5gqkwkv9k94nvxe562exrey7lrcjmzqegn2wspgcke4", 79 "test_address_nimiq": "NQ288KG7ER5QUANFN5X1J1CJFRN6FE8GC1KM", 80 "test_address_nano": "nano_1mbtirc4x3kixfy5wufxaqakd3gbojpn6gpmk6kjiyngnjwgy6yty3txgztq" 81}; 82 83// ============================================ 84// CORE HELPERS 85// ============================================ 86 87// Creates a typed array with 8-bit unsigned integers from a byte array 88function uint_8array(bytes) { 89 return new Uint8Array(bytes); 90} 91 92// Encodes string to UTF-8 using TextEncoder 93function buffer(enc) { 94 return utf8_encoder.encode(enc); 95} 96 97// Decodes UTF-8 encoded data using TextDecoder 98function unbuffer(enc) { 99 return utf8_decoder.decode(enc); 100} 101 102// Converts ArrayBuffer to zero-padded hexadecimal string 103function buf2hex(buffer) { 104 return Array.prototype.map.call(uint_8array(buffer), x => ("00" + x.toString(16)).slice(-2)).join(""); 105} 106 107// Validates string contains only hexadecimal characters [0-9a-fA-F] 108function is_hex(str) { 109 return /^[a-fA-F0-9]+$/.test(str); 110} 111 112// Left-pads string with zeros to specified byte length with truncation 113function str_pad(val, bytes) { 114 return (bytestring.slice(0, bytes) + val).slice(-bytes); 115} 116 117// Converts integer to base-16 string representation 118function dec_to_hex(val) { 119 return val.toString(16); 120} 121 122// Parses hexadecimal string to BigInt with 0x prefix 123function hex_to_dec(val) { 124 return BigInt("0x" + val); 125} 126 127// Converts a hexadecimal string to a decimal string 128function hex_to_number_string(val) { 129 return hex_to_int(val).toString(); 130} 131 132// Converts a hexadecimal string to a number 133function hex_to_int(val) { 134 return parseInt(val, 16); 135} 136 137// Pads binary strings with leading zeros 138function pad_binary(binary_str, target_length) { 139 let padded_str = binary_str.toString(); 140 while (padded_str.length < target_length) { 141 padded_str = "0" + padded_str; 142 } 143 return padded_str; 144} 145 146// Concatenates multiple Uint8Arrays into one 147function concat_bytes(...arrays) { 148 const sizes = arrays.reduce((acc, a) => acc + a.length, 0), 149 result = uint_8array(sizes); 150 let offset = 0; 151 for (const array of arrays) { 152 result.set(array, offset); 153 offset += array.length; 154 } 155 return result; 156} 157
158// Encodes integer as Bitcoin-style variable-length integer (LEB128) 159function encode_varint(n) { 160 const bytes = []; 161 while (n >= 0x80) { 162 bytes.push((n & 0x7f) | 0x80); 163 n >>= 7; 164 } 165 bytes.push(n); 166 return new Uint8Array(bytes); 167} 168 169// ============================================ 170// SJCL BIT OPERATIONS 171// ============================================ 172 173// Converts UTF-8 string to SJCL bit array 174function to_bits(val) { 175 return sjcl.codec.utf8String.toBits(val); 176} 177 178// Converts hex string to SJCL bit array 179function hex_to_bits(val) { 180 return sjcl.codec.hex.toBits(val); 181} 182 183// Converts SJCL bit array to hex string 184function from_bits(val) { 185 return sjcl.codec.hex.fromBits(val); 186} 187 188// Returns the bit length of SJCL bit array 189function bit_length(val) { 190 return sjcl.bitArray.bitLength(val); 191} 192 193// Concatenates two SJCL bit arrays 194function concat_array(arr1, arr2) { 195 return sjcl.bitArray.concat(arr1, arr2); 196} 197 198// ============================================ 199// BASE CONVERSION 200// ============================================ 201 202// Converts binary string to SJCL word array 203function binary_string_to_word_array(binary) { 204 const bit_len = binary.length; 205 let words = []; 206 for (let i = 0; i < bit_len; i += 32) { 207 const str_chunk = binary.substring(i, i + 32), 208 int_word = parseInt(str_chunk, 2); 209 words.push(int_word | 0); 210 } 211 return sjcl.bitArray.clamp(words, bit_len); 212} 213 214// Converts byte array to SJCL word array 215function byte_array_to_word_array(data) { 216 let words = [], 217 i, 218 word = 0; 219 for (i = 0; i < data.length; i++) { 220 word = (word << 8) | data[i]; 221 if ((i + 1) % 4 === 0) { 222 words.push(word); 223 word = 0; 224 } 225 } 226 if (i % 4 !== 0) { 227 word <<= (4 - (i % 4)) * 8; 228 words.push(word); 229 } 230 return sjcl.bitArray.clamp(words, data.length * 8); 231} 232 233// Converts byte array to binary string 234function byte_array_to_binary_string(data) { 235 let bin_str = ""; 236 for (let i = 0; i < data.length; i++) { 237 bin_str += pad_binary(data[i].toString(2), 8); 238 } 239 return bin_str; 240} 241 242// Converts hex string to binary string 243function hex_string_to_binary_string(hexString) { 244 let bin_str = ""; 245 for (let i = 0; i < hexString.length; i++) { 246 const hexChar = hexString[i], 247 hexInt = parseInt(hexChar, 16), 248 bin_frag = hexInt.toString(2); 249 bin_str += pad_binary(bin_frag, 4); 250 } 251 return bin_str; 252} 253 254// ============================================ 255// BASE58 ENCODING 256// ============================================ 257 258// Encodes data to Base58 string from hex or UTF-8 input 259function b58enc(enc, encode = "hex") { 260 const bytestring = (encode === "hex") ? hex_to_bytes(enc) : buffer(enc); 261 return b58enc_uint_array(bytestring); 262} 263 264// Converts Uint8Array to Base58 string using custom alphabet 265function b58enc_uint_array(u) { 266 let d = [], 267 s = "", 268 i, j, c, n; 269 for (i in u) { 270 j = 0, c = u[i]; 271 s += c || s.length ^ i ? "" : 1; 272 while (j in d || c) { 273 n = d[j]; 274 n = n ? n * 256 + c : c; 275 c = n / 58 | 0; 276 d[j] = n % 58; 277 j++ 278 } 279 } 280 while (j--) s += b58ab[d[j]]; 281 return s; 282} 283 284// Decodes Base58 string to UTF-8 or hexadecimal output 285function b58dec(dec, decode) { 286 const buffer = b58dec_uint_array(dec); 287 return (decode === "hex") ? buf2hex(buffer) : unbuffer(buffer); 288} 289 290// Converts Base58 string to Uint8Array using custom alphabet 291function b58dec_uint_array(dec) { 292 let d = [], 293 b = [], 294 i, j, c, n; 295 for (i in dec) { 296 j = 0, c = b58ab.indexOf(dec[i]); 297 if (c < 0) return undefined; 298 c || b.length ^ i ? i : b.push(0); 299 while (j in d || c) { 300 n = d[j]; 301 n = n ? n * 58 + c : c; 302 c = n >> 8; 303 d[j] = n % 256; 304 j++ 305 } 306 } 307 while (j--) b.push(d[j]); 308 return uint_8array(b); 309} 310 311// Implements Base58Check encoding with double SHA256 checksum 312function b58check_encode(payload) { 313 const full_bytes = payload + hmacsha(hmacsha(payload, "sha256", "hex"), "sha256", "hex").slice(0, 8); 314 return b58enc(full_bytes, "hex"); 315} 316 317// Decodes Base58Check string and removes 4-byte checksum 318function b58check_decode(val) { 319 const full_bytes = b58dec(val, "hex"), 320 bytes = full_bytes.substring(0, full_bytes.length - 8); 321 return bytes; 322} 323 324// Verifies the 4-byte Base58Check checksum of a 78-byte extended key 325function b58check_valid(val) { 326 try { 327 const full_bytes = b58dec(val, "hex"); 328 if (full_bytes.length !== 164) return false;
329 const payload = full_bytes.slice(0, -8), 330 checksum = hmacsha(hmacsha(payload, "sha256", "hex"), "sha256", "hex").slice(0, 8); 331 return checksum === full_bytes.slice(-8); 332 } catch (e) { 333 return false; 334 } 335} 336 337// ============================================ 338// BECH32 ENCODING 339// ============================================ 340 341// Converts input byte array to 5-bit word representation for bech32 encoding 342function to_words(bytes) { 343 const res = convert_bits(bytes, 8, 5, true); 344 if (Array.isArray(res)) { 345 return res; 346 } 347 throw new Error(res) 348} 349 350// Converts 5-bit word array back to byte representation for bech32 decoding 351function from_words(bytes) { 352 const res = convert_bits(bytes, 5, 8, true); 353 if (Array.isArray(res)) { 354 return res; 355 } 356 throw new Error(res) 357} 358 359// Transforms data between different bit-length representations with optional padding 360function convert_bits(data, inBits, outBits, pad) { 361 let value = 0, 362 bits = 0, 363 maxV = (1 << outBits) - 1, 364 result = []; 365 for (let i = 0; i < data.length; ++i) { 366 value = (value << inBits) | data[i]; 367 bits += inBits; 368 while (bits >= outBits) { 369 bits -= outBits; 370 result.push((value >> bits) & maxV); 371 } 372 } 373 if (pad) { 374 if (bits > 0) { 375 result.push((value << (outBits - bits)) & maxV); 376 } 377 } else { 378 if (bits >= inBits) { 379 return "Excess padding"; 380 } 381 if ((value << (outBits - bits)) & maxV) { 382 return "Non-zero padding"; 383 } 384 } 385 return result; 386} 387 388// Computes the Bech32 checksum 389function polymod(values) {
390 let chk = 1; 391 for (let p = 0; p < values.length; ++p) { 392 let top = chk >> 25; 393 chk = (chk & 0x1ffffff) << 5 ^ values[p]; 394 for (let i = 0; i < 5; ++i) { 395 if ((top >> i) & 1) { 396 chk ^= generator[i]; 397 } 398 } 399 } 400 return chk; 401} 402 403// Expands the human-readable part for Bech32 encoding 404function hrp_expand(hrp) { 405 const ret = []; 406 let p; 407 for (p = 0; p < hrp.length; ++p) { 408 ret.push(hrp.charCodeAt(p) >> 5); 409 } 410 ret.push(0); 411 for (p = 0; p < hrp.length; ++p) { 412 ret.push(hrp.charCodeAt(p) & 31); 413 } 414 return ret; 415} 416 417// Verifies the checksum in a Bech32 address 418function verify_checksum(hrp, data) { 419 return polymod(hrp_expand(hrp).concat(data)) === 1; 420} 421 422// Creates a checksum for Bech32 encoding 423function create_checksum(hrp, data) { 424 const values = hrp_expand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]), 425 mod = polymod(values) ^ 1, 426 ret = []; 427 for (let p = 0; p < 6; ++p) { 428 ret.push((mod >> 5 * (5 - p)) & 31); 429 } 430 return ret; 431} 432 433// Encodes data into a Bech32 address 434function bech32_encode(hrp, data) {
435 let combined = data.concat(create_checksum(hrp, data)), 436 ret = hrp + "1"; 437 for (let p = 0; p < combined.length; ++p) { 438 ret += b32ab.charAt(combined[p]); 439 } 440 return ret; 441} 442 443// Creates a checksum for Bech32m encoding (Taproot / Spark addresses) 444function create_checksum_m(hrp, data) { 445 const values = hrp_expand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]), 446 mod = polymod(values) ^ BECH32M_CONST, 447 ret = []; 448 for (let p = 0; p < 6; ++p) { 449 ret.push((mod >> 5 * (5 - p)) & 31); 450 } 451 return ret; 452} 453 454// Encodes data into a Bech32m address 455function bech32m_encode(hrp, data) { 456 let combined = data.concat(create_checksum_m(hrp, data)), 457 ret = hrp + "1"; 458 for (let p = 0; p < combined.length; ++p) { 459 ret += b32ab.charAt(combined[p]); 460 } 461 return ret; 462} 463 464// Decodes a Bech32 encoded string 465function bech32_decode(bechString) { 466 let p, has_lower = false, 467 has_upper = false; 468 469 for (p = 0; p < bechString.length; ++p) { 470 if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { 471 return null; 472 } 473 if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { 474 has_lower = true; 475 } 476 if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { 477 has_upper = true; 478 } 479 } 480 if (has_lower && has_upper) { 481 return null; 482 } 483 484 bechString = bechString.toLowerCase(); 485 const pos = bechString.lastIndexOf("1"); 486 if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { 487 return null; 488 } 489 490 const hrp = bechString.substring(0, pos), 491 data = []; 492 493 for (p = pos + 1; p < bechString.length; ++p) { 494 const d = b32ab.indexOf(bechString.charAt(p)); 495 if (d === -1) { 496 return null; 497 } 498 data.push(d); 499 } 500 501 const encoding = verify_checksum_with_type(hrp, data); 502 if (!encoding) { 503 return null; 504 } 505 506 if (data[0] === 1 && encoding !== "bech32m") { 507 return null; 508 } 509 if (data[0] === 0 && encoding !== "bech32") { 510 return null; 511 } 512 return { 513 hrp, 514 "data": data.slice(0, data.length - 6), 515 encoding 516 }; 517} 518 519// Modified polymod function to support both bech32 and bech32m 520function verify_checksum_with_type(hrp, data) { 521 const modulo = polymod(hrp_expand(hrp).concat(data)); 522 if (modulo === BECH32_CONST) return "bech32"; 523 if (modulo === BECH32M_CONST) return "bech32m"; 524 return null; 525} 526 527// Converts a binary array to decimal array for Bech32 encoding 528function bech32_dec_array(bitarr) { 529 const hexstr = [0]; 530 bitarr.forEach(bits => { 531 hexstr.push(parseInt(bits, 2)); 532 }); 533 return hexstr; 534} 535 536// Converts a public key to a Bech32 address 537function pub_to_address_bech32(hrp, pubkey) { 538 const step1 = hash160(pubkey), 539 step2 = hex_string_to_binary_string(step1), 540 step3 = step2.match(/.{1,5}/g), 541 step4 = bech32_dec_array(step3); 542 return bech32_encode(hrp, step4); 543} 544 545// ============================================ 546// KASPA BECH32 (8-character checksum variant) 547// ============================================ 548 549// Kaspa uses a modified bech32 with 8-character checksum (40-bit) 550// Generator polynomial split into high 8 bits and low 32 bits for JavaScript compatibility 551// 40-bit BCH polymod shared by the CashAddr and Kaspa checksum schemes 552function cashaddr_polymod(values) { 553 const GENERATORS = [ 554 0x98f2bc8e61n, 0x79b76d99e2n, 0xf33e5fb3c4n, 555 0xae2eabe2a8n, 0x1e4f43e470n 556 ]; 557 let chk = 1n; 558 for (let i = 0; i < values.length; i++) { 559 const top = chk >> 35n; 560 chk = ((chk & 0x07ffffffffn) << 5n) ^ BigInt(values[i]); 561 for (let j = 0; j < 5; j++) { 562 if ((top >> BigInt(j)) & 1n) { 563 chk ^= GENERATORS[j]; 564 } 565 } 566 } 567 return chk ^ 1n; 568} 569 570// Prefix expansion shared by CashAddr and Kaspa: low 5 bits of each char, then 0 571function cashaddr_prefix_expand(prefix) { 572 const result = []; 573 for (let i = 0; i < prefix.length; i++) {
574 result.push(prefix.charCodeAt(i) & 31); 575 } 576 result.push(0); 577 return result; 578} 579 580// Converts a 40-bit polymod result to 8 five-bit checksum values 581function cashaddr_checksum_array(mod) { 582 const checksum = new Array(8); 583 for (let i = 0; i < 8; i++) { 584 checksum[7 - i] = Number((mod >> BigInt(i * 5)) & 31n); 585 } 586 return checksum; 587} 588 589// Kaspa polymod (numeric result, same generator polynomial as CashAddr) 590function kaspa_polymod(data) { 591 return Number(cashaddr_polymod(data)); 592} 593 594// Create 8-character checksum for Kaspa addresses 595function kaspa_create_checksum(prefix, data) { 596 const checksumData = cashaddr_prefix_expand(prefix).concat(data).concat([0, 0, 0, 0, 0, 0, 0, 0]); 597 return cashaddr_checksum_array(cashaddr_polymod(checksumData)); 598} 599 600// Converts a compressed public key to a Kaspa address 601function pub_to_kaspa_address(pubkey) { 602 // Extract x-only pubkey (32 bytes) from compressed pubkey (33 bytes) 603 const x_only = pubkey.slice(2), 604 pubkey_bytes = []; 605 for (let i = 0; i < x_only.length; i += 2) { 606 pubkey_bytes.push(parseInt(x_only[i] + x_only[i + 1], 16)); 607 } 608 // Version 0x00 = schnorr pubkey 609 const version = 0x00, 610 data_bytes = [version].concat(pubkey_bytes), 611 words = to_words(data_bytes), 612 checksum = kaspa_create_checksum("kaspa", words), 613 combined = words.concat(checksum); 614 let address = "kaspa:"; 615 for (let i = 0;
615 i < combined.length; i++) { 616 address += b32ab.charAt(combined[i]); 617 } 618 return address; 619} 620 621// ============================================ 622// SECP256K1 ELLIPTIC CURVE 623// ============================================ 624 625// Computes modular reduction with positive result 626function mod(a, m = CURVE.P) { 627 const r = a % m; 628 return r >= 0n ? r : m + r; 629} 630 631// Evaluates the secp256k1 curve equation y² = x³ + 7 for a given x coordinate 632function weierstrass(x) { 633 return mod(x ** 3n + CURVE.b); 634} 635 636// Implements Extended Euclidean Algorithm to find GCD and Bézout's identity coefficients 637function egcd(a, b) { 638 if (typeof a === "number") a = BigInt(a); 639 if (typeof b === "number") b = BigInt(b); 640 let [x, y, u, v] = [0n, 1n, 1n, 0n]; 641 while (a !== 0n) { 642 const q = b / a, 643 r = b % a; 644 let m = x - u * q, 645 n = y - v * q; 646 [b, a] = [a, r]; 647 [x, y] = [u, v]; 648 [u, v] = [m, n]; 649 } 650 return [b, x, y]; 651} 652 653// Calculates the modular multiplicative inverse using extended Euclidean algorithm 654function invert(number, modulo = CURVE.P) { 655 if (number === 0n || modulo <= 0n) { 656 throw new Error("invert: invalid number"); 657 } 658 const [g, x] = egcd(mod(number, modulo), modulo); 659 if (g !== 1n) throw new Error("invert: does not exist"); 660 return mod(x, modulo); 661} 662 663// Converts hexadecimal string to Uint8Array with zero-padding for odd length 664function hex_to_bytes(hex) { 665 if (typeof hex !== "string") throw new TypeError("hexToBytes: expected string"); 666 if (hex.length % 2) hex = "0" + hex; 667 const len = hex.length / 2, 668 array = uint_8array(len); 669 for (let i = 0; i < len; i++) { 670 const j = i * 2; 671 array[i] = parseInt(hex.slice(j, j + 2), 16); 672 } 673 return array; 674} 675 676// Converts Uint8Array to hexadecimal string 677function bytes_to_hex(uint8a) { 678 let hex = ""; 679 for (let i = 0; i < uint8a.length; i++) { 680 hex += uint8a[i].toString(16).padStart(2, "0"); 681 } 682 return hex; 683} 684 685// Parses hexadecimal string to BigInt 686function hex_to_number(hex) { 687 if (typeof hex !== "string") throw new TypeError("hexToNumber: expected string"); 688 return hex.length ? BigInt("0x" + hex) : 0n; 689} 690 691// Converts Uint8Array to BigInt (big-endian) 692function bytes_to_number(bytes) { 693 return hex_to_number(bytes_to_hex(bytes)); 694} 695 696// Zero-pads BigInt to 64-character hex string 697function pad64(num) { 698 return num.toString(16).padStart(64, "0"); 699} 700 701// Implements point addition and doubling in Jacobian projective coordinates 702class JacobianPoint { 703 constructor(x, y, z) { 704 this.x = x; 705 this.y = y; 706 this.z = z; 707 } 708 709 static fromAffine(p) { 710 return new JacobianPoint(p.x, p.y, 1n); 711 } 712 713 double() { 714 const { 715 "x": X1, 716 "y": Y1, 717 "z": Z1 718 } = this; 719 if (!Y1) return new JacobianPoint(0n, 0n, 0n); 720 const A = mod(X1 ** 2n), 721 B = mod(Y1 ** 2n), 722 C = mod(B ** 2n), 723 D = mod(2n * (mod((X1 + B) ** 2n) - A - C)), 724 E = mod(3n * A), 725 F = mod(E ** 2n), 726 X3 = mod(F - 2n * D), 727 Y3 = mod(E * (D - X3) - 8n * C), 728 Z3 = mod(2n * Y1 * Z1); 729 return new JacobianPoint(X3, Y3, Z3); 730 } 731 732 add(other) { 733 if (!other.x && !other.y) return this; 734 if (!this.x && !this.y) return other; 735 const { 736 "x": X1, 737 "y": Y1, 738 "z": Z1 739 } = this, { 740 "x": X2, 741 "y": Y2, 742 "z": Z2 743 } = other, Z1Z1 = mod(Z1 ** 2n), Z2Z2 = mod(Z2 ** 2n), U1 = mod(X1 * Z2Z2), U2 = mod(X2 * Z1Z1), S1 = mod(Y1 * Z2 * Z2Z2), S2 = mod(Y2 * Z1 * Z1Z1), H = mod(U2 - U1), r = mod(S2 - S1); 744 if (H === 0n) { 745 if (r === 0n) { 746 return this.double(); 747 } else { 748 return new JacobianPoint(0n, 0n, 0n); 749 } 750 } 751 const HH = mod(H ** 2n), 752 HHH = mod(H * HH), 753 V = mod(U1 * HH), 754 X3 = mod(r ** 2n - HHH - 2n * V), 755 Y3 = mod(r * (V - X3) - S1 * HHH), 756 Z3 = mod(Z1 * Z2 * H); 757 return new JacobianPoint(X3, Y3, Z3); 758 } 759 760 multiplyUnsafe(scalar) { 761 let n = scalar; 762 if (typeof n !== "bigint") n = BigInt(n); 763 n = n % CURVE.n; 764 if (n === 0n) return new JacobianPoint(0n, 0n, 0n); 765 let p = new JacobianPoint(0n, 0n, 0n), 766 d = this; 767 while (n > 0n) { 768 if (n & 1n) p = p.add(d); 769 d = d.double(); 770 n >>= 1n; 771 } 772 return p; 773 } 774 775 toAffine() { 776 if (this.z === 0n) { 777 return new Point(0n, 0n); 778 } 779 const iz = invert(this.z, CURVE.P), 780 iz2 = mod(iz ** 2n), 781 x = mod(this.x * iz2), 782 y = mod(this.y * iz2 * iz); 783 return new Point(x, y); 784 } 785} 786 787// Represents a point on secp256k1 curve in affine coordinates (x,y) 788class Point { 789 constructor(x, y) { 790 this.x = x; 791 this.y = y; 792 } 793 794 static fromPrivateKey(privateKey) {
795 const key = normalize_privatekey(privateKey); 796 return Point.BASE.multiply(key); 797 } 798 799 static fromHex(hex) { 800 const bytes = hex instanceof Uint8Array ? hex : hex_to_bytes(hex); 801 if (bytes.length === 32) { 802 return this.fromX(bytes); 803 } 804 const header = bytes[0]; 805 if (header === 0x02 || header === 0x03) { 806 return this.fromCompressedHex(bytes); 807 } 808 if (header === 0x04) { 809 return this.fromUncompressedHex(bytes); 810 } 811 throw new Error("Point.fromHex: invalid format"); 812 } 813 814 static fromX(bytes) { 815 const x = bytes_to_number(bytes), 816 y2 = weierstrass(x); 817 let y = sqrt_mod(y2); 818 if ((y & 1n) === 1n) { 819 y = mod(-y); 820 } 821 const p = new Point(x, y); 822 p.assertValidity(); 823 return p; 824 } 825 826 static fromCompressedHex(bytes) { 827 if (bytes.length !== 33) { 828 throw new Error("Compressed pubkey must be 33 bytes"); 829 } 830 const x = bytes_to_number(bytes.slice(1)), 831 y2 = weierstrass(x); 832 let y = sqrt_mod(y2); 833 const odd = (y & 1n) === 1n, 834 isFirstByteOdd = (bytes[0] & 1) === 1; 835 if (odd !== isFirstByteOdd) { 836 y = mod(-y); 837 } 838 const p = new Point(x, y); 839 p.assertValidity(); 840 return p; 841 } 842 843 static fromUncompressedHex(bytes) { 844 if (bytes.length !== 65) { 845 throw new Error("Uncompressed pubkey must be 65 bytes"); 846 } 847 const x = bytes_to_number(bytes.slice(1, 33)), 848 y = bytes_to_number(bytes.slice(33)), 849 p = new Point(x, y); 850 p.assertValidity(); 851 return p; 852 } 853 854 assertValidity() { 855 const { 856 x, 857 y 858 } = this; 859 if (x < 0n || x >= CURVE.P || y < 0n || y >= CURVE.P) { 860 throw new Error("Point is not on curve (coordinates out of range)"); 861 } 862 const left = mod(y * y), 863 right = weierstrass(x); 864 if (left !== right) { 865 throw new Error("Point is not on curve (y^2 != x^3 + 7)"); 866 } 867 } 868 869 multiply(scalar) { 870 return JacobianPoint.fromAffine(this).multiplyUnsafe(scalar).toAffine(); 871 } 872 873 add(other) { 874 const pA = JacobianPoint.fromAffine(this), 875 pB = JacobianPoint.fromAffine(other); 876 return pA.add(pB).toAffine(); 877 } 878 879 negate() { 880 return new Point(this.x, mod(-this.y)); 881 } 882 883 toHex(compressed = false) { 884 const xHex = pad64(this.x); 885 if (compressed) { 886 const prefix = (this.y & 1n) === 1n ? "03" : "02"; 887 return prefix + xHex; 888 } else { 889 const yHex = pad64(this.y); 890 return "04" + xHex + yHex; 891 } 892 } 893 894 static get BASE() { 895 return new Point(CURVE.Gx, CURVE.Gy); 896 } 897 898 static get ZERO() { 899 return new Point(0n, 0n); 900 } 901} 902 903// Precomputed constant for sqrt_mod 904const P_1_4 = (CURVE.P + 1n) >> 2n; 905 906// Calculates modular square root using simplified Tonelli-Shanks for p â¡ 3 (mod 4) 907function sqrt_mod(x) { 908 return pow_mod(x, P_1_4, CURVE.P); 909} 910 911// Computes modular exponentiation using square-and-multiply algorithm 912function pow_mod(base, exponent, modulus) { 913 let result = 1n, 914 b = mod(base, modulus), 915 e = exponent; 916 while (e > 0n) { 917 if (e & 1n) result = mod(result * b, modulus); 918 b = mod(b * b, modulus); 919 e >>= 1n; 920 } 921 return result; 922} 923 924// Validates and normalizes private key to BigInt within curve order range 925function normalize_privatekey(privateKey) { 926 let key = null; 927 if (typeof privateKey === "bigint") { 928 key = privateKey; 929 } else if (typeof privateKey === "string") { 930 key = hex_to_number(privateKey); 931 } else if (privateKey instanceof Uint8Array) { 932 key = bytes_to_number(privateKey); 933 } else { 934 throw new Error("Invalid private key type"); 935 } 936 key = key % CURVE.n; 937 if (key <= 0n || key >= CURVE.n) { 938 throw new Error("Invalid private key range"); 939 } 940 return key; 941} 942 943// Derives compressed or uncompressed public key from private key scalar 944function get_publickey(privateKey, isCompressed = true) { 945 const P = Point.fromPrivateKey(privateKey); 946 return P.toHex(isCompressed); 947} 948 949// Export the main Point class 950secp.Point = Point; 951 952// ============================================ 953// HASH FUNCTIONS 954// ============================================ 955 956// Generates HMAC using SJCL with optional hex encoding 957function hmac_bits(message, key, encode) { 958 const enc_msg = (encode == "hex") ? hex_to_bits(message) : message, 959 hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
960 return from_bits(hmac.encrypt(enc_msg)); 961} 962 963// Computes HMAC-SHA hash with optional key encoding 964function hmacsha(key, hash, encode) { 965 const enc_key = (encode == "hex") ? hex_to_bits(key) : key; 966 return from_bits(hmacsha_bits(enc_key, hash)); 967} 968 969// Performs HMAC-SHA hash computation on input key 970function hmacsha_bits(key, hash) { 971 return sjcl.hash[hash].hash(key); 972} 973 974// Computes double hash: RIPEMD160(SHA256(input)) 975function hash160(pub) { 976 return hmacsha(hmacsha(pub, "sha256", "hex"), "ripemd160", "hex"); 977} 978 979// Computes a substring of SHA256 hash 980function sha_sub(val, lim) { 981 return hmacsha(val, "sha256").slice(0, lim); 982} 983 984// aes_enc prepare 985function secret_to_key(secret) { 986 return sjcl.codec.base64.fromBits(sjcl.hash.sha256.hash(secret)); 987} 988 989function aes_encode(data, secret) { 990 const payload = typeof data === "string" ? data : JSON.stringify(data); 991 return aes_enc(btoa(unescape(encodeURIComponent(payload))), secret_to_key(secret)); 992} 993 994function aes_decode(encrypted, secret) { 995 const decrypted = aes_dec(encrypted, secret_to_key(secret)); 996 if (!decrypted) return null; 997 try { 998 const raw = atob(strip_quotes(decrypted)); 999 let payload; 1000 try { 1001 payload = decodeURIComponent(escape(raw)); // new UTF-8 payloads 1002 } catch (e) { 1003 payload = raw; // legacy plain-btoa payloads 1004 } 1005 return JSON.parse(payload); 1006 } catch (e) { 1007 return decrypted; // not base64/JSON at all 1008 } 1009} 1010 1011// Keccak-256 hash function (used for Ethereum addresses) 1012function keccak_256(input) { 1013 const rc = [ 1014 1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 1015 32907, 0, 2147483649, 0, 2147516545, 2147483648, 32777, 2147483648, 1016 138, 0, 136, 0, 2147516425, 0, 2147483658, 0, 1017 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771, 2147483648, 1018 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648, 1019 2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648 1020 ]; 1021 1022 function keccak_f(s) { 1023 let c = [], 1024 b = [], 1025 h, l; 1026 for (let n = 0; n < 48; n += 2) { 1027 for (let x = 0; x < 10; x++) { 1028 c[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40]; 1029 } 1030 for (let x = 0; x < 10; x += 2) { 1031 h = c[(x + 8) % 10] ^ (c[(x + 2) % 10] << 1 | c[(x + 3) % 10] >>> 31); 1032 l = c[(x + 9) % 10] ^ (c[(x + 3) % 10] << 1 | c[(x + 2) % 10] >>> 31); 1033 for (let y = 0; y < 50; y += 10) { 1034 s[x + y] ^= h; 1035 s[x + y + 1] ^= l; 1036 } 1037 } 1038 b[0] = s[0]; 1039 b[1] = s[1]; 1040 b[32] = s[11] << 4 | s[10] >>> 28; 1041 b[33] = s[10] << 4 | s[11] >>> 28; 1042 b[14] = s[20] << 3 | s[21] >>> 29; 1043 b[15] = s[21] << 3 | s[20] >>> 29; 1044 b[46] = s[31] << 9 | s[30] >>> 23; 1045 b[47] = s[30] << 9 | s[31] >>> 23; 1046 b[28] = s[40] << 18 | s[41] >>> 14; 1047 b[29] = s[41] << 18 | s[40] >>> 14; 1048 b[20] = s[2] << 1 | s[3] >>> 31; 1049 b[21] = s[3] << 1 | s[2] >>> 31; 1050 b[2] = s[13] << 12 | s[12] >>> 20; 1051 b[3] = s[12] << 12 | s[13] >>> 20; 1052 b[34] = s[22] << 10 | s[23] >>> 22; 1053 b[35] = s[23] << 10 | s[22] >>> 22; 1054 b[16] = s[33] << 13 | s[32] >>> 19; 1055 b[17] = s[32] << 13 | s[33] >>> 19; 1056 b[48] = s[42] << 2 | s[43] >>> 30; 1057 b[49] = s[43] << 2 | s[42] >>> 30; 1058 b[40] = s[5] << 30 | s[4] >>> 2; 1059 b[41] = s[4] << 30 | s[5] >>> 2; 1060 b[22] = s[14] << 6 | s[15] >>> 26; 1061 b[23] = s[15] << 6 | s[14] >>> 26; 1062 b[4] = s[25] << 11 | s[24] >>> 21; 1063 b[5] = s[24] << 11 | s[25] >>> 21; 1064 b[36] = s[34] << 15 | s[35] >>> 17; 1065 b[37] = s[35] << 15 | s[34] >>> 17; 1066 b[18] = s[45] << 29 | s[44] >>> 3; 1067 b[19] = s[44] << 29 | s[45] >>> 3; 1068 b[10] = s[6] << 28 | s[7] >>> 4; 1069 b[11] = s[7] << 28 | s[6] >>> 4; 1070 b[42] = s[17] << 23 | s[16] >>> 9; 1071 b[43] = s[16] << 23 | s[17] >>> 9; 1072 b[24] = s[26] << 25 | s[27] >>> 7; 1073 b[25] = s[27] << 25 | s[26] >>> 7; 1074 b[6] = s[36] << 21 | s[37] >>> 11; 1075 b[7] = s[37] << 21 | s[36] >>> 11; 1076 b[38] = s[47] << 24 | s[46] >>> 8; 1077 b[39] = s[46] << 24 | s[47] >>> 8; 1078 b[30] = s[8] << 27 | s[9] >>> 5; 1079 b[31] = s[9] << 27 | s[8] >>> 5; 1080 b[12] = s[18] << 20 | s[19] >>> 12; 1081 b[13] = s[19] << 20 | s[18] >>> 12; 1082 b[44] = s[29] << 7 | s[28] >>> 25; 1083 b[45] = s[28] << 7 | s[29] >>> 25; 1084 b[26] = s[38] << 8 | s[39] >>> 24; 1085 b[27] = s[39] << 8 | s[38] >>> 24; 1086 b[8] = s[48] << 14 | s[49] >>> 18; 1087 b[9] = s[49] << 14 | s[48] >>> 18; 1088 for (let y = 0; y < 50; y += 10) { 1089 for (let x = 0; x < 10; x += 2) { 1090 s[y + x] = b[y + x] ^ (~b[y + (x + 2) % 10] & b[y + (x + 4) % 10]); 1091 s[y + x + 1] = b[y + x + 1] ^ (~b[y + (x + 3) % 10] & b[y + (x + 5) % 10]); 1092 } 1093 } 1094 s[0] ^= rc[n]; 1095 s[1] ^= rc[n + 1]; 1096 } 1097 } 1098 1099 let bytes; 1100 if (typeof input === "string") { 1101 bytes = new Uint8Array(input.length); 1102 for (let i = 0; i < input.length; i++) bytes[i] = input.charCodeAt(i); 1103 } else if (input instanceof Uint8Array) { 1104 bytes = input; 1105 } else if (Array.isArray(input)) { 1106 bytes = new Uint8Array(input); 1107 } else { 1108 throw new Error("Invalid input type for keccak256"); 1109 } 1110 1111 const rate = 136, 1112 block_count = 34, 1113 s = new Array(50).fill(0), 1114 blocks = new Array(35).fill(0); 1115 let i = 0; 1116 1117 for (let pos = 0; pos < bytes.length; pos++) { 1118 blocks[i >> 2] |= bytes[pos] << ((i & 3) << 3); 1119 if (++i >= rate) { 1120 for (let j = 0; j < block_count; j++) { 1121 s[j] ^= blocks[j]; 1122 blocks[j] = 0; 1123 } 1124 keccak_f(s); 1125 i = 0; 1126 } 1127 } 1128 1129 blocks[i >> 2] |= 1 << ((i & 3) << 3); 1130 blocks[block_count - 1] |= 0x80000000; 1131 for (let j = 0; j < block_count; j++) s[j] ^= blocks[j]; 1132 keccak_f(s); 1133 1134 let hex = ""; 1135 for (let i = 0; i < 32; i++) { 1136 const byte = (s[i >> 2] >> ((i & 3) << 3)) & 0xff;
1137 hex += (byte >> 4).toString(16) + (byte & 0xf).toString(16); 1138 } 1139 return hex; 1140} 1141 1142// ============================================ 1143// KEY & ADDRESS GENERATION 1144// ============================================ 1145 1146// Encodes private key to Wallet Import Format (WIF) with optional compression 1147function privkey_wif(versionbytes, hexkey, comp) { 1148 const compressed = (comp) ? "01" : ""; 1149 return b58check_encode(versionbytes + hexkey + compressed); 1150} 1151 1152// Generates corresponding public key from a private key 1153function priv_to_pub(priv) { 1154 return get_publickey(priv, true); 1155} 1156 1157// Converts compressed public key to full uncompressed format 1158function expand_pub(pub) { 1159 return secp.Point.fromHex(pub).toHex(false); 1160} 1161 1162// Generates standard cryptocurrency address from public key 1163function pub_to_address(versionbytes, pub) { 1164 return hash160_to_address(versionbytes, hash160(pub)); 1165} 1166 1167// Derives Ethereum-specific address from public key 1168function pub_to_eth_address(pub) { 1169 const xp_pub = expand_pub(pub), 1170 keccak = "0x" + keccak_256(hex_to_bytes(xp_pub.slice(2))), 1171 addr = "0x" + keccak.slice(26); 1172 return to_checksum_address(addr); 1173} 1174 1175// Converts RIPEMD160 hash to a cryptocurrency address 1176function hash160_to_address(versionbytes, h160) { 1177 return b58check_encode(versionbytes + h160); 1178} 1179 1180// Converts an Ethereum address to checksum format 1181function to_checksum_address(e) { 1182 if (void 0 === e) { 1183 return ""; 1184 } 1185 if (!/^(0x)?[0-9a-f]{40}$/i.test(e)) { 1186 throw new Error("Given address " + e + " is not a valid Ethereum address."); 1187 } 1188 e = e.toLowerCase().replace(/^0x/i, ""); 1189 const t = keccak_256(e).replace(/^0x/i, ""); 1190 let r = "0x"; 1191 for (let n = 0; n < e.length; n++) 1192 7 < parseInt(t[n], 16) ? r += e[n].toUpperCase() : r += e[n]; 1193 return r; 1194} 1195 1196// ============================================ 1197// BITCOIN CASH (CASHADDR) 1198// ============================================ 1199 1200const cashaddr = (function() { 1201 const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l", 1202 CHARSET_MAP = {}; 1203 for (let i = 0; i < CHARSET.length; i++) { 1204 CHARSET_MAP[CHARSET[i]] = i; 1205 } 1206 1207 function convertBits(data, fromBits, toBits, pad) { 1208 const result = convert_bits(data, fromBits, toBits, pad); 1209 if (typeof result === "string") { 1210 throw new Error(result); 1211 } 1212 return new Uint8Array(result); 1213 } 1214 1215 function createChecksum(prefix, payload) { 1216 const combined = cashaddr_prefix_expand(prefix).concat(Array.from(payload), [0, 0, 0, 0, 0, 0, 0, 0]); 1217 return new Uint8Array(cashaddr_checksum_array(cashaddr_polymod(combined))); 1218 } 1219 1220 function verifyChecksum(prefix, payload) { 1221 const combined = cashaddr_prefix_expand(prefix).concat(Array.from(payload)); 1222 return cashaddr_polymod(combined) === 0n; 1223 } 1224 1225 function getHashSize(versionByte) { 1226 return [160, 192, 224, 256, 320, 384, 448, 512][versionByte & 7]; 1227 } 1228 1229 function getType(versionByte) { 1230 const typeValue = versionByte & 120; 1231 if (typeValue === 0) return "P2PKH"; 1232 if (typeValue === 8) return "P2SH"; 1233 throw new Error("Invalid address type"); 1234 } 1235 1236 return { 1237 "encode": function(prefix, type, hash) { 1238 let versionByte = (type === "P2PKH") ? 0 : (type === "P2SH") ? 8 : null; 1239 if (versionByte === null) throw new Error("Invalid type: " + type); 1240 1241 const hashBits = hash.length * 8, 1242 sizeMap = { 1243 "160": 0, 1244 "192": 1, 1245 "224": 2, 1246 "256": 3, 1247 "320": 4, 1248 "384": 5, 1249 "448": 6, 1250 "512": 7 1251 }; 1252 if (!(hashBits in sizeMap)) throw new Error("Invalid hash size"); 1253 versionByte |= sizeMap[hashBits]; 1254 1255 const versionAndHash = new Uint8Array(hash.length + 1); 1256 versionAndHash[0] = versionByte; 1257 versionAndHash.set(hash, 1); 1258 1259 const payload = convertBits(versionAndHash, 8, 5, true), 1260 checksum = createChecksum(prefix, payload), 1261 combined = new Uint8Array(payload.length + checksum.length); 1262 combined.set(payload); 1263 combined.set(checksum, payload.length); 1264 1265 let result = prefix + ":"; 1266 for (let i = 0;
1266 i < combined.length; i++) { 1267 result += CHARSET[combined[i]]; 1268 } 1269 return result; 1270 }, 1271 1272 "decode": function(address) { 1273 const lower = address.toLowerCase(); 1274 if (address !== lower && address !== address.toUpperCase()) { 1275 throw new Error("Mixed case address"); 1276 } 1277 const parts = lower.split(":"); 1278 if (parts.length !== 2) throw new Error("Missing prefix"); 1279 1280 const prefix = parts[0], 1281 payloadStr = parts[1], 1282 payload = new Uint8Array(payloadStr.length); 1283 for (let i = 0; i < payloadStr.length; i++) { 1284 const char = payloadStr[i]; 1285 if (!(char in CHARSET_MAP)) throw new Error("Invalid character: " + char); 1286 payload[i] = CHARSET_MAP[char]; 1287 } 1288 1289 if (!verifyChecksum(prefix, payload)) throw new Error("Invalid checksum"); 1290 1291 const data = payload.slice(0, -8), 1292 converted = convertBits(data, 5, 8, false), 1293 versionByte = converted[0], 1294 hash = converted.slice(1); 1295 1296 if (hash.length * 8 !== getHashSize(versionByte)) { 1297 throw new Error("Invalid hash size"); 1298 } 1299 1300 return { 1301 prefix, 1302 type: getType(versionByte), 1303 hash 1304 }; 1305 } 1306 }; 1307})(); 1308 1309// Converts a legacy Bitcoin Cash address to CashAddr format 1310function pub_to_cashaddr(legacy) { 1311 const c_addr = bch_cashaddr("bitcoincash", "P2PKH", legacy); 1312 return c_addr.split(":")[1]; 1313} 1314 1315// Converts a CashAddr format address to legacy Bitcoin Cash address 1316function bch_legacy(cadr) { 1317 try { 1318 const address = (cadr.indexOf(":") === -1) ? "bitcoincash:" + cadr : cadr, 1319 dec = cashaddr.decode(address), 1320 bytes = dec.hash, 1321 bytesarr = Array.from(bytes), 1322 conc = concat_array([dec.type === "P2SH" ? 5 : 0], bytesarr), 1323 unbuf = buf2hex(conc); 1324 return b58check_encode(unbuf); 1325 } catch (e) { 1326 return cadr 1327 } 1328} 1329 1330// Converts a legacy Bitcoin Cash address to CashAddr format 1331function bch_cashaddr(prefix, type, legacy) { 1332 try { 1333 const lbytes = b58dec_uint_array(legacy), 1334 lbslice = lbytes.slice(1, 21); 1335 return cashaddr.encode(prefix, type, lbslice); 1336 } catch (e) { 1337 console.error(e.name, e.message); 1338 return legacy; 1339 } 1340} 1341 1342// ============================================ 1343// LNURL & LIGHTNING 1344// ============================================ 1345 1346// Decodes a Bech32 encoded LNURL 1347function lnurl_decodeb32(lnurl) { 1348 let p, 1349 has_lower = false, 1350 has_upper = false;
1351 for (p = 0; p < lnurl.length; ++p) { 1352 if (lnurl.charCodeAt(p) < 33 || lnurl.charCodeAt(p) > 126) { 1353 return null; 1354 } 1355 if (lnurl.charCodeAt(p) >= 97 && lnurl.charCodeAt(p) <= 122) { 1356 has_lower = true; 1357 } 1358 if (lnurl.charCodeAt(p) >= 65 && lnurl.charCodeAt(p) <= 90) { 1359 has_upper = true; 1360 } 1361 } 1362 if (has_lower && has_upper) { 1363 return null; 1364 } 1365 const lnurlow = lnurl.toLowerCase(), 1366 pos = lnurlow.lastIndexOf("1"), 1367 hrp = lnurlow.substring(0, pos), 1368 data = []; 1369 for (p = pos + 1; p < lnurlow.length; ++p) { 1370 const d = b32ab.indexOf(lnurlow.charAt(p)); 1371 if (d === -1) { 1372 return null; 1373 } 1374 data.push(d); 1375 } 1376 if (!verify_checksum(hrp, data)) { 1377 return null; 1378 } 1379 return { 1380 "hrp": hrp, 1381 "data": data.slice(0, data.length - 6) 1382 }; 1383} 1384 1385// ============================================ 1386// MNEMONIC FUNCTIONS 1387// ============================================ 1388 1389// Cleans and normalizes mnemonic string 1390function clean_string(words) { 1391 return normalize_string(join_words(split_words(words))); 1392} 1393 1394// Concatenates word array with single space delimiter 1395function join_words(words) { 1396 return words.join(" "); 1397} 1398 1399// Splits string on whitespace and removes empty elements 1400function split_words(mnemonic) { 1401 return mnemonic.split(/\s/g).filter(function(x) { 1402 return x.length; 1403 }); 1404} 1405 1406// Applies Unicode NFKD normalization to string 1407function normalize_string(str) { 1408 return str.normalize("NFKD"); 1409} 1410 1411// Converts BIP39 mnemonic to binary string with 11-bit word indices 1412function mnemonic_to_binary_string(mnemonic) { 1413 const mm = split_words(mnemonic); 1414 if (mm.length == 0 || mm.length % 3 > 0) { 1415 return null; 1416 } 1417 const idx = []; 1418 for (let i = 0; i < mm.length; i++) { 1419 const word = mm[i], 1420 wordIndex = wordlist.indexOf(word); 1421 if (wordIndex == -1) { 1422 return null; 1423 } 1424 const binaryIndex = pad_binary(wordIndex.toString(2), 11); 1425 idx.push(binaryIndex); 1426 } 1427 return idx.join(""); 1428} 1429 1430// ============================================ 1431// ENCRYPTION 1432// ============================================ 1433 1434// Encrypts data using AES-GCM 1435function aes_enc(params, keyString) { 1436 const buffer = uint_8array(16), 1437 iv = byte_array_to_word_array(crypto.getRandomValues(buffer)), 1438 key = sjcl.codec.base64.toBits(keyString), 1439 cipher = new sjcl.cipher.aes(key), 1440 data = to_bits(params), 1441 enc = sjcl.mode.gcm.encrypt(cipher, data, iv, {}, 128), 1442 concatbitArray = concat_array(iv, enc), 1443 conString = sjcl.codec.base64.fromBits(concatbitArray); 1444 return conString; 1445} 1446 1447// Decrypts AES-GCM encrypted data 1448function aes_dec(content, keyst) { 1449 const bitArray = sjcl.codec.base64.toBits(content), 1450 bitArrayCopy = bitArray.slice(0), 1451 ivdec = bitArrayCopy.slice(0, 4), 1452 encryptedBitArray = bitArray.slice(4), 1453 key = sjcl.codec.base64.toBits(keyst), 1454 cipher = new sjcl.cipher.aes(key); 1455 try { 1456 const data = sjcl.mode.gcm.decrypt(cipher, encryptedBitArray, ivdec, {}, 128); 1457 return sjcl.codec.utf8String.fromBits(data); 1458 } catch (err) { 1459 console.error(err.name, err.message); 1460 return false; 1461 } 1462} 1463 1464// ============================================ 1465// SCRIPTHASH 1466// ============================================ 1467 1468// Convert address to scripthash, with support for Bitcoin Cash addresses 1469function address_to_scripthash(addr, currency) { 1470 const address = (currency === "bitcoin-cash") ? bch_legacy(addr) : addr; 1471 let script_pub_key; 1472 1473 if (address.startsWith("bc1") || address.startsWith("tb1") || address.startsWith("ltc1")) { 1474 try { 1475 const decoded = bech32_decode(address); 1476 if (!decoded) throw new Error("Invalid bech32 address"); 1477 const program = convert5to8(decoded.data.slice(1)); 1478 if (!program) throw new Error("Invalid witness program"); 1479 if (decoded.data[0] === 1) { 1480 if (program.length !== 32) { 1481 throw new Error("Invalid Taproot program length: " + program.length); 1482 } 1483 script_pub_key = "5120" + program.map(function(b) { 1484 return b.toString(16).padStart(2, "0"); 1485 }).join(""); 1486 } else if (decoded.data[0] === 0) { 1487 if (program.length === 20) { 1488 script_pub_key = "0014" + program.map(function(b) { 1489 return b.toString(16).padStart(2, "0"); 1490 }).join(""); 1491 } else if (program.length === 32) { 1492 script_pub_key = "0020" + program.map(function(b) { 1493 return b.toString(16).padStart(2, "0"); 1494 }).join(""); 1495 } else { 1496 throw new Error("Invalid witness program length: " + program.length); 1497 } 1498 } else { 1499 throw new Error("Unsupported witness version"); 1500 } 1501 } catch (error) { 1502 throw new Error("Invalid bech32 address: " + error.message); 1503 } 1504 } else { 1505 try {
1506 const decoded = b58check_decode(address), 1507 version = decoded.slice(0, 2), 1508 hash = decoded.slice(2); 1509 1510 if (version === "00" || version === "30") { 1511 script_pub_key = "76a914" + hash + "88ac"; 1512 } else if (version === "05" || version === "32") { 1513 script_pub_key = "a914" + hash + "87"; 1514 } else { 1515 throw new Error("Unsupported address version: " + version); 1516 } 1517 } catch (error) { 1518 throw new Error("Invalid base58 address: " + error.message); 1519 } 1520 } 1521 1522 const script_hash = hmacsha(script_pub_key, "sha256", "hex"); 1523 return { 1524 "script_pub_key": script_pub_key, 1525 "hash": script_hash.match(/.{2}/g).reverse().join("") 1526 }; 1527} 1528 1529// Helper function for converting groups of 5 bits to 8 bits 1530function convert5to8(data) { 1531 const acc = new Array(Math.floor(data.length * 5 / 8)); 1532 let index = 0, 1533 bits = 0, 1534 value = 0; 1535 for (let i = 0; i < data.length; ++i) { 1536 value = (value << 5) | data[i]; 1537 bits += 5; 1538 while (bits >= 8) { 1539 bits -= 8; 1540 acc[index] = (value >> bits) & 0xff; 1541 index += 1; 1542 } 1543 } 1544 if (bits >= 5 || ((value << (8 - bits)) & 0xff)) { 1545 return null; 1546 } 1547 return acc; 1548} 1549 1550// ============================================ 1551// COMPATIBILITY TESTING 1552// ============================================ 1553 1554// Tests crypto.getRandomValues availability 1555function test_crypto_api() { 1556 try { 1557 return !!(crypto && crypto.getRandomValues); 1558 } catch (e) { 1559 console.error("CryptoUtils test_crypto_api:", e.message); 1560 return false; 1561 } 1562} 1563 1564// Tests BigInt functionality 1565function test_bigint() { 1566 try { 1567 return typeof BigInt === "function" && 1568 BigInt("9007199254740991") + BigInt(1) === BigInt("9007199254740992"); 1569 } catch (e) { 1570 console.error("CryptoUtils test_bigint:", e.message); 1571 return false; 1572 } 1573} 1574 1575// Tests secp256k1 private key to public key derivation 1576function test_secp256k1() { 1577 try { 1578 return get_publickey(crypto_utils_const.test_privkey) === crypto_utils_const.test_pubkey; 1579 } catch (e) { 1580 console.error("CryptoUtils test_secp256k1:", e.message); 1581 return false; 1582 } 1583} 1584 1585// Tests bech32 address encoding 1586function test_bech32() { 1587 try { 1588 return pub_to_address_bech32("bc", crypto_utils_const.test_pubkey_bech32) === crypto_utils_const.test_address_bech32; 1589 } catch (e) { 1590 console.error("CryptoUtils test_bech32:", e.message); 1591 return false; 1592 } 1593} 1594 1595// Tests Bitcoin Cash cashaddr encoding 1596function test_cashaddr() { 1597 try { 1598 return pub_to_cashaddr(crypto_utils_const.test_bch_legacy_address) === crypto_utils_const.test_bch_cashaddr; 1599 } catch (e) { 1600 console.error("CryptoUtils test_cashaddr:", e.message); 1601 return false; 1602 } 1603} 1604 1605// Tests keccak256 / Ethereum address derivation 1606function test_keccak256() { 1607 try { 1608 return pub_to_eth_address(crypto_utils_const.test_pubkey_eth) === crypto_utils_const.test_address_eth; 1609 } catch (e) { 1610 console.error("CryptoUtils test_keccak256:", e.message); 1611 return false; 1612 } 1613} 1614 1615// Tests Kaspa address encoding 1616function test_kaspa() { 1617 const generated = pub_to_kaspa_address(crypto_utils_const.test_pubkey_kaspa); 1618 if (generated === crypto_utils_const.test_address_kaspa) { 1619 return true; 1620 } 1621 return false; 1622} 1623 1624// Tests AES encryption round-trip 1625function test_aes() { 1626 try { 1627 const test_data = "crypto_utils_test", 1628 test_key = "0123456789abcdef0123456789abcdef", 1629 encrypted = aes_enc(test_data, test_key); 1630 return aes_dec(encrypted, test_key) === test_data; 1631 } catch (e) { 1632 console.error("CryptoUtils test_aes:", e.message); 1633 return false; 1634 } 1635} 1636 1637// ============================================ 1638// BLAKE2B-256 1639// Pure vanilla JS, BigInt for 64-bit word ops 1640// ============================================ 1641 1642const BLAKE2B_IV = [
1643 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 1644 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n, 1645 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 1646 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n 1647]; 1648 1649const BLAKE2B_SIGMA = [ 1650 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 1651 [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], 1652 [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], 1653 [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], 1654 [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], 1655 [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], 1656 [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], 1657 [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], 1658 [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], 1659 [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0] 1660]; 1661 1662const MASK_64 = 0xffffffffffffffffn; 1663 1664function b2b_rotr64(x, n) { 1665 return ((x >> BigInt(n)) | (x << BigInt(64 - n))) & MASK_64; 1666} 1667 1668function b2b_G(v, a, b, c, d, x, y) { 1669 v[a] = (v[a] + v[b] + x) & MASK_64; 1670 v[d] = b2b_rotr64(v[d] ^ v[a], 32); 1671 v[c] = (v[c] + v[d]) & MASK_64; 1672 v[b] = b2b_rotr64(v[b] ^ v[c], 24); 1673 v[a] = (v[a] + v[b] + y) & MASK_64; 1674 v[d] = b2b_rotr64(v[d] ^ v[a], 16); 1675 v[c] = (v[c] + v[d]) & MASK_64; 1676 v[b] = b2b_rotr64(v[b] ^ v[c], 63); 1677} 1678 1679function b2b_compress(h, block, t, last) { 1680 const v = new Array(16); 1681 for (let i = 0; i < 8; i++) { 1682 v[i] = h[i]; 1683 v[i + 8] = BLAKE2B_IV[i]; 1684 } 1685 v[12] ^= t & MASK_64; 1686 v[13] ^= (t >> 64n) & MASK_64; 1687 if (last) { 1688 v[14] ^= MASK_64; 1689 } 1690 const m = new Array(16); 1691 for (let i = 0; i < 16; i++) { 1692 const off = i * 8; 1693 m[i] = BigInt(block[off]) | (BigInt(block[off + 1]) << 8n) | 1694 (BigInt(block[off + 2]) << 16n) | (BigInt(block[off + 3]) << 24n) | 1695 (BigInt(block[off + 4]) << 32n) | (BigInt(block[off + 5]) << 40n) | 1696 (BigInt(block[off + 6]) << 48n) | (BigInt(block[off + 7]) << 56n); 1697 } 1698 for (let i = 0; i < 12; i++) { 1699 const s = BLAKE2B_SIGMA[i % 10]; 1700 b2b_G(v, 0, 4, 8, 12, m[s[0]], m[s[1]]); 1701 b2b_G(v, 1, 5, 9, 13, m[s[2]], m[s[3]]); 1702 b2b_G(v, 2, 6, 10, 14, m[s[4]], m[s[5]]); 1703 b2b_G(v, 3, 7, 11, 15, m[s[6]], m[s[7]]); 1704 b2b_G(v, 0, 5, 10, 15, m[s[8]], m[s[9]]); 1705 b2b_G(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); 1706 b2b_G(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); 1707 b2b_G(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); 1708 } 1709 for (let i = 0; i < 8; i++) { 1710 h[i] ^= v[i] ^ v[i + 8]; 1711 } 1712} 1713 1714// Blake2b: Uint8Array in â Uint8Array out (default 32 bytes) 1715function blake2b(input, outlen) { 1716 if (outlen === undefined) outlen = 32; 1717 const h = [...BLAKE2B_IV]; 1718 h[0] ^= 0x01010000n | BigInt(outlen); // fanout=1, depth=1, digest_length=outlen 1719 const len = input.length; 1720 if (len > 128) { 1721 const full = Math.floor((len - 1) / 128); 1722 for (let i = 0; i < full; i++) { 1723 b2b_compress(h, input.slice(i * 128, (i + 1) * 128), BigInt((i + 1) * 128), false); 1724 } 1725 const last = new Uint8Array(128); 1726 last.set(input.slice(full * 128)); 1727 b2b_compress(h, last, BigInt(len), true); 1728 } else { 1729 const block = new Uint8Array(128); 1730 block.set(input); 1731 b2b_compress(h, block, BigInt(len), true); 1732 } 1733 const out = new Uint8Array(outlen); 1734 for (let i = 0; i < outlen; i++) { 1735 out[i] = Number((h[Math.floor(i / 8)] >> BigInt((i % 8) * 8)) & 0xffn); 1736 } 1737 return out; 1738} 1739 1740// ============================================ 1741// ED25519 ELLIPTIC CURVE ARITHMETIC 1742// Self-contained implementation for Ed25519 pubkey derivation 1743// Curve: -x² + y² = 1 + d·x²·y² over GF(2²âµâµ - 19) 1744// ============================================ 1745 1746const ED25519 = { 1747 "a": -1n, 1748 "d": 37095705934669439343138083508754565189542113879843219016388785533085940283555n, 1749 "P": 2n ** 255n - 19n, 1750 "n": 2n ** 252n + 27742317777372353535851937790883648493n, 1751 "Gx": 15112221349535400772501151409588531511454012693041857206046113283949847762202n, 1752 "Gy": 46316835694926478169428394003475163141307993866256225615783033603165251855960n 1753 }, 1754 ED25519_ENCODING_LENGTH = 32, 1755 ed_DIV_8_MINUS_3 = (ED25519.P + 3n) / 8n, 1756 ed_pointPrecomputes = new WeakMap(); 1757 1758let ed_I_cached = null; 1759 1760// Little-endian bytes to BigInt
1761function ed_bytes_to_number_le(uint8a) { 1762 let num = 0n; 1763 for (let i = uint8a.length - 1; i >= 0; i--) { 1764 num = (num << 8n) | BigInt(uint8a[i]); 1765 } 1766 return num; 1767} 1768 1769// Batch modular inverse (Montgomery's trick) 1770function ed_invert_batch(nums, n = ED25519.P) { 1771 const len = nums.length, 1772 scratch = new Array(len); 1773 let acc = 1n; 1774 for (let i = 0; i < len; i++) { 1775 if (nums[i] === 0n) continue; 1776 scratch[i] = acc; 1777 acc = mod(acc * nums[i], n); 1778 } 1779 acc = invert(acc, n); 1780 for (let i = len - 1; i >= 0; i--) { 1781 if (nums[i] === 0n) continue; 1782 const tmp = mod(acc * nums[i], n); 1783 nums[i] = mod(acc * scratch[i], n); 1784 acc = tmp; 1785 } 1786 return nums; 1787} 1788 1789// Ed25519 Extended Point (x, y, z, t) for efficient group operations 1790class EdExtPoint { 1791 constructor(x, y, z, t) { 1792 this.x = x; 1793 this.y = y; 1794 this.z = z; 1795 this.t = t; 1796 } 1797 1798 static fromAffine(p) { 1799 if (!(p instanceof EdPoint)) 1800 throw new TypeError("EdExtPoint#fromAffine: expected EdPoint"); 1801 if (p.equals(EdPoint.ZERO)) return EdExtPoint.ZERO; 1802 return new EdExtPoint(p.x, p.y, 1n, mod(p.x * p.y, ED25519.P)); 1803 } 1804 1805 static toAffineBatch(points) { 1806 const toInv = ed_invert_batch(points.map(p => p.z)); 1807 return points.map((p, i) => p.toAffine(toInv[i])); 1808 } 1809 1810 static normalizeZ(points) { 1811 return this.toAffineBatch(points).map(this.fromAffine); 1812 } 1813 1814 negate() { 1815 return new EdExtPoint(mod(-this.x, ED25519.P), this.y, this.z, mod(-this.t, ED25519.P)); 1816 } 1817 1818 double() { 1819 const P = ED25519.P, 1820 X1 = this.x, 1821 Y1 = this.y, 1822 Z1 = this.z, 1823 A = mod(X1 ** 2n, P), 1824 B = mod(Y1 ** 2n, P), 1825 C = mod(2n * Z1 ** 2n, P), 1826 D = mod(ED25519.a * A, P), 1827 E = mod((X1 + Y1) ** 2n - A - B, P), 1828 G = mod(D + B, P), 1829 F = mod(G - C, P), 1830 H = mod(D - B, P); 1831 return new EdExtPoint(mod(E * F, P), mod(G * H, P), mod(F * G, P), mod(E * H, P)); 1832 } 1833 1834 add(other) { 1835 const P = ED25519.P, 1836 X1 = this.x, 1837 Y1 = this.y, 1838 Z1 = this.z, 1839 T1 = this.t, 1840 X2 = other.x, 1841 Y2 = other.y, 1842 Z2 = other.z, 1843 T2 = other.t, 1844 A = mod((Y1 - X1) * (Y2 + X2), P), 1845 B = mod((Y1 + X1) * (Y2 - X2), P), 1846 F = mod(B - A, P); 1847 if (F === 0n) return this.double(); 1848 const C = mod(Z1 * 2n * T2, P), 1849 D = mod(T1 * 2n * Z2, P), 1850 E = mod(D + C, P), 1851 G = mod(B + A, P), 1852 H = mod(D - C, P); 1853 return new EdExtPoint(mod(E * F, P), mod(G * H, P), mod(F * G, P), mod(E * H, P)); 1854 } 1855 1856 precomputeWindow(W) { 1857 const windows = 256 / W + 1; 1858 let points = [], 1859 p = this, 1860 base = p; 1861 for (let window = 0; window < windows; window++) { 1862 base = p; 1863 points.push(base); 1864 for (let i = 1; i < 2 ** (W - 1); i++) { 1865 base = base.add(p); 1866 points.push(base); 1867 } 1868 p = base.double(); 1869 } 1870 return points; 1871 } 1872 1873 wNAF(n, affinePoint) { 1874 if (!affinePoint && this.equals(EdExtPoint.BASE)) 1875 affinePoint = EdPoint.BASE; 1876 const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; 1877 if (256 % W) throw new Error("EdPoint#wNAF: Invalid precomputation window"); 1878 let precomputes = affinePoint && ed_pointPrecomputes.get(affinePoint); 1879 if (!precomputes) { 1880 precomputes = this.precomputeWindow(W); 1881 if (affinePoint && W !== 1) { 1882 precomputes = EdExtPoint.normalizeZ(precomputes); 1883 ed_pointPrecomputes.set(affinePoint, precomputes); 1884 } 1885 } 1886 let p = EdExtPoint.ZERO, 1887 f = EdExtPoint.ZERO; 1888 const windows = 256 / W + 1, 1889 windowSize = 2 ** (W - 1), 1890 mask = BigInt(2 ** W - 1), 1891 maxNumber = 2 ** W, 1892 shiftBy = BigInt(W); 1893 for (let window = 0; window < windows; window++) { 1894 const offset = window * windowSize; 1895 let wbits = Number(n & mask); 1896 n >>= shiftBy; 1897 if (wbits > windowSize) { 1898 wbits -= maxNumber; 1899 n += 1n; 1900 } 1901 if (wbits === 0) { 1902 f = f.add(window % 2 ? precomputes[offset].negate() : precomputes[offset]); 1903 } else { 1904 const cached = precomputes[offset + Math.abs(wbits)
1904- 1]; 1905 p = p.add(wbits < 0 ? cached.negate() : cached); 1906 } 1907 } 1908 return [p, f]; 1909 } 1910 1911 multiply(scalar, affinePoint) { 1912 if (typeof scalar !== "number" && typeof scalar !== "bigint") 1913 throw new TypeError("EdPoint#multiply: expected number or bigint"); 1914 const n = mod(BigInt(scalar), ED25519.n); 1915 if (n <= 0) throw new Error("EdPoint#multiply: invalid scalar"); 1916 return EdExtPoint.normalizeZ(this.wNAF(n, affinePoint))[0]; 1917 } 1918 1919 toAffine(invZ = invert(this.z, ED25519.P)) { 1920 return new EdPoint(mod(this.x * invZ, ED25519.P), mod(this.y * invZ, ED25519.P)); 1921 } 1922 1923 equals(other) { 1924 return this.x === other.x && this.y === other.y && this.z === other.z && this.t === other.t; 1925 } 1926} 1927EdExtPoint.ZERO = new EdExtPoint(0n, 1n, 1n, 0n); 1928 1929// Ed25519 Affine Point (x, y) â user-facing representation 1930class EdPoint { 1931 constructor(x, y) { 1932 this.x = x; 1933 this.y = y; 1934 } 1935 1936 _setWindowSize(windowSize) { 1937 this._WINDOW_SIZE = windowSize; 1938 ed_pointPrecomputes.delete(this); 1939 } 1940 1941 static fromHex(hash) { 1942 const bytes = hash instanceof Uint8Array ? hash : hex_to_bytes(hash), 1943 len = bytes.length - 1, 1944 normedLast = bytes[len] & ~0x80, 1945 isLastByteOdd = (bytes[len] & 0x80) !== 0, 1946 normed = Uint8Array.from(Array.from(bytes.slice(0, len)).concat(normedLast)), 1947 y = ed_bytes_to_number_le(normed); 1948 if (y >= ED25519.P) throw new Error("EdPoint#fromHex expects hex <= Fp"); 1949 const sqrY = y * y, 1950 sqrX = mod((sqrY - 1n) * invert(ED25519.d * sqrY + 1n, ED25519.P), ED25519.P); 1951 if (ed_I_cached === null) 1952 ed_I_cached = pow_mod(2n, (ED25519.P + 1n) / 4n, ED25519.P); 1953 let x = pow_mod(sqrX, ed_DIV_8_MINUS_3, ED25519.P); 1954 if (mod(x * x - sqrX, ED25519.P) !== 0n) 1955 x = mod(x * ed_I_cached, ED25519.P); 1956 if ((x & 1n) === 1n !== isLastByteOdd) 1957 x = mod(-x, ED25519.P); 1958 return new EdPoint(x, y); 1959 } 1960 1961 toRawBytes() { 1962 const hex = this.y.toString(16).padStart(1 + (this.y.toString(16).length | 1), "0"), 1963 u8 = uint_8array(ED25519_ENCODING_LENGTH); 1964 for (let i = hex.length - 2, j = 0; j < ED25519_ENCODING_LENGTH && i >= 0; i -= 2, j++) 1965 u8[j] = parseInt(hex[i] + hex[i + 1], 16); 1966 if (this.x & 1n) u8[ED25519_ENCODING_LENGTH - 1] |= 0x80; 1967 return u8; 1968 } 1969 1970 toHex() { 1971 return bytes_to_hex(this.toRawBytes()); 1972 } 1973 equals(other) { 1974 return this.x === other.x && this.y === other.y; 1975 } 1976 add(other) { 1977 return EdExtPoint.fromAffine(this).add(EdExtPoint.fromAffine(other)).toAffine(); 1978 } 1979 multiply(scalar) { 1980 return EdExtPoint.fromAffine(this).multiply(scalar, this).toAffine(); 1981 } 1982} 1983EdPoint.BASE = new EdPoint(ED25519.Gx, ED25519.Gy); 1984EdPoint.ZERO = new EdPoint(0n, 1n); 1985EdPoint.BASE._setWindowSize(8); 1986 1987// Scalar à G base point multiplication 1988function ed25519_point_multiply(hex) { 1989 return EdPoint.BASE.multiply(EdPoint.fromHex(hex).y); 1990} 1991 1992// ============================================ 1993// ED25519 PUBLIC KEY DERIVATION 1994// Standard (SHA-512) and Nano variant (Blake2b-512) 1995// ============================================ 1996 1997// Standard Ed25519 seed â public key (SHA-512 hash, clamp, scalar à G) 1998function ed25519_pubkey(privkey) { 1999 const hash = from_bits(sjcl.hash.sha512.hash(hex_to_bits(privkey))), 2000 scalar_bytes = hex_to_bytes(hash.slice(0, 64)); 2001 scalar_bytes[0] &= 248; 2002 scalar_bytes[31] &= 127; 2003 scalar_bytes[31] |= 64; 2004 return ed25519_point_multiply(bytes_to_hex(scalar_bytes)).toHex(); 2005} 2006 2007// Nano Ed25519: uses Blake2b-512 instead of SHA-512 2008function nano_ed25519_pubkey(privkey) { 2009 const hash = blake2b(hex_to_bytes(privkey), 64), 2010 scalar_bytes = hash.slice(0, 32); 2011 scalar_bytes[0] &= 248; 2012 scalar_bytes[31] &= 127; 2013 scalar_bytes[31] |= 64; 2014 return ed25519_point_multiply(bytes_to_hex(scalar_bytes)).toHex(); 2015} 2016 2017// ============================================ 2018// NIMIQ + NANO ADDRESS ENCODING 2019// Nimiq: Custom base32 + IBAN MOD-97-10 checksum 2020// Nano: Custom base32 + Blake2b-5 checksum 2021// ============================================ 2022 2023// Hex â Nimiq custom base32 string 2024function hex_to_nimiq_base32(hex) { 2025 let num = BigInt("0x" + hex), 2026 result = ""; 2027 while (num > 0n) { 2028 result = NIMIQ_ALPHABET[Number(num % 32n)] + result; 2029 num = num / 32n; 2030 } 2031 return result; 2032} 2033 2034// IBAN MOD-97-10 checksum for Nimiq address 2035function nimiq_iban_checksum(base32_str) { 2036 const raw = base32_str + "NQ00"; 2037 let numeric = ""; 2038 for (let i = 0; i < raw.length; i++) { 2039 const c = raw.charCodeAt(i); 2040 numeric += (c >= 48 && c <= 57) ? raw[i] : (c - 55).toString(); 2041 } 2042 let remainder = 0; 2043 for (let i = 0; i < numeric.length; i++) { 2044 remainder = (remainder * 10 + parseInt(numeric[i])) % 97; 2045 } 2046 return str_pad((98 - remainder).toString(), 2); 2047} 2048 2049// 20-byte raw address hex â full Nimiq address 2050function to_nimiq_address(raw_hex) { 2051 const base32 = hex_to_nimiq_base32(raw_hex), 2052 padded = base32.padStart(32, NIMIQ_ALPHABET[0]), 2053 checksum = nimiq_iban_checksum(padded); 2054 return "NQ" + checksum + padded; 2055} 2056 2057// Encode Uint8Array to Nano base32 2058// Bit-packing: reads bits MSB-first, emits 5-bit groups 2059function encode_nano_base32(input) { 2060 const len = input.length, 2061 leftover = (8 * len) % 5, 2062 pad = leftover === 0 ? 0 : 5 - leftover; 2063 let bits = 0, 2064 result = "", 2065 count = 0; 2066 for (let i = 0; i < len; i++) { 2067 bits = (bits << 8) | input[i]; 2068 count += 8; 2069 while (count >= 5) { 2070 result += NANO_ALPHABET[(bits >>> (count + pad - 5)) & 31]; 2071 count -= 5; 2072 } 2073 } 2074 if (count > 0) {
2075 result += NANO_ALPHABET[(bits << (5 - (count + pad))) & 31]; 2076 } 2077 return result; 2078} 2079 2080// Public key hex â full Nano address 2081function to_nano_address(pubkey_hex) { 2082 const pubkey_bytes = hex_to_bytes(pubkey_hex), 2083 checksum = blake2b(pubkey_bytes, 5).reverse(), 2084 body = encode_nano_base32(pubkey_bytes), 2085 check = encode_nano_base32(checksum); 2086 return "nano_" + body + check; 2087} 2088 2089// NANO to RAW conversion (1 NANO = 10^30 RAW) 2090function nano_to_raw(amount) { 2091 const str = amount.toString(), 2092 dot = str.indexOf("."); 2093 if (dot === -1) { 2094 return str + "0".repeat(30); 2095 } 2096 const integer = str.slice(0, dot), 2097 fraction = str.slice(dot + 1).slice(0, 30).padEnd(30, "0"); 2098 return (integer + fraction).replace(/^0+/, "") || "0"; 2099} 2100 2101// ============================================ 2102// MODULE EXPORT 2103// ============================================ 2104 2105const CryptoUtils = { 2106 // Library info 2107 VERSION: "1.1.0", 2108 2109 // Curve parameters 2110 secp, 2111 CURVE: CURVE, 2112 2113 // === Core Helpers === 2114 uint_8array, 2115 buffer, 2116 unbuffer, 2117 buf2hex, 2118 is_hex, 2119 str_pad, 2120 dec_to_hex, 2121 hex_to_dec, 2122 hex_to_number_string, 2123 hex_to_int, 2124 pad_binary, 2125 pad64, 2126 concat_bytes, 2127 encode_varint, 2128 2129 // === SJCL Bit Operations === 2130 to_bits, 2131 hex_to_bits, 2132 from_bits, 2133 bit_length, 2134 concat_array, 2135 2136 // === String Utilities === 2137 clean_string, 2138 normalize_string, 2139 split_words, 2140 join_words, 2141 2142 // === Base Conversion === 2143 binary_string_to_word_array, 2144 byte_array_to_word_array, 2145 byte_array_to_binary_string, 2146 hex_string_to_binary_string, 2147 mnemonic_to_binary_string, 2148 2149 // === Base58 === 2150 b58enc, 2151 b58enc_uint_array, 2152 b58dec, 2153 b58dec_uint_array, 2154 b58check_encode, 2155 b58check_decode, 2156 b58check_valid, 2157 2158 // === Bech32 === 2159 to_words, 2160 from_words, 2161 convert_bits, 2162 polymod, 2163 hrp_expand, 2164 verify_checksum, 2165 verify_checksum_with_type, 2166 create_checksum, 2167 bech32_encode, 2168 bech32m_encode, 2169 create_checksum_m, 2170 bech32_decode, 2171 bech32_dec_array, 2172 lnurl_decodeb32, 2173 kaspa_polymod, 2174 kaspa_create_checksum, 2175 2176 // === Byte/Hex Conversion === 2177 hex_to_bytes, 2178 bytes_to_hex, 2179 hex_to_number, 2180 bytes_to_number, 2181 2182 // === Elliptic Curve (secp256k1) === 2183 mod, 2184 weierstrass, 2185 egcd, 2186 invert, 2187 pow_mod, 2188 sqrt_mod, 2189 2190 // === Key Operations === 2191 normalize_privatekey, 2192 get_publickey, 2193 priv_to_pub, 2194 privkey_wif, 2195 expand_pub, 2196 2197 // === Key Operations === 2198 ed25519_pubkey, 2199 nano_ed25519_pubkey, 2200 ed25519_point_multiply, 2201 ed_bytes_to_number_le, 2202 EdPoint, 2203 ED25519, 2204 2205 // === Hashing === 2206 hmacsha, 2207 hmac_bits, 2208 hmacsha_bits, 2209 sha_sub, 2210 keccak_256, 2211 hash160, 2212 blake2b, 2213 2214 // === Address Generation === 2215 pub_to_address, 2216 pub_to_address_bech32, 2217 pub_to_kaspa_address, 2218 hash160_to_address, 2219 pub_to_eth_address, 2220 to_checksum_address, 2221 to_nano_address, 2222 to_nimiq_address, 2223 nano_to_raw, 2224
2225 // === Bitcoin Cash === 2226 pub_to_cashaddr, 2227 bch_cashaddr, 2228 bch_legacy, 2229 2230 // === Encryption === 2231 aes_enc, 2232 aes_dec, 2233 2234 // === Validation === 2235 address_to_scripthash, 2236 convert5to8, 2237 2238 // === Constants === 2239 crypto_utils_const, 2240 2241 // === Testing === 2242 test_crypto_api, 2243 test_bigint, 2244 test_secp256k1, 2245 test_bech32, 2246 test_cashaddr, 2247 test_keccak256, 2248 test_kaspa, 2249 test_aes 2250};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.