PageSourceSearch

https://bitrequest.github.io/assets/js/lib/crypto_utils.js

js bitrequest.github.io collected 2026-10-03 08:43:07 UTC 69,945 bytes, 2,250 lines download raw bytes

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.