├── .github ├── funding.yml └── workflows │ ├── release.yml │ └── test-js.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── SECURITY.md ├── audit ├── 2022-01-05-cure53-audit-nbl2.pdf └── README.md ├── index.ts ├── jsr.json ├── lib └── esm │ └── package.json ├── package-lock.json ├── package.json ├── test ├── base58.test.js ├── bases.test.js ├── bech32.test.js ├── benchmark │ ├── index.js │ ├── package.json │ └── quadratic.js ├── bip173.test.js ├── build │ ├── input.js │ ├── package-lock.json │ └── package.json ├── deno.ts ├── generator-rust │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── bin │ │ └── genTestsBases.rs │ │ ├── lib.rs │ │ └── utils.rs ├── index.js ├── package.json ├── rfc4648.test.js ├── slow-dos.test.js ├── tsconfig.json ├── utils.js ├── utils.test.js └── vectors │ ├── base58.json │ ├── base58_check.json │ ├── base58_check.json.LICENSE │ ├── base58_xmr.json │ ├── base58_xmr.json.LICENSE │ └── base_vectors.json ├── tsconfig.cjs.json └── tsconfig.json /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: paulmillr 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | release-js: 7 | name: 'jsbt v0.3.3' # Should match commit below 8 | uses: paulmillr/jsbt/.github/workflows/release.yml@c9a9f2cd6b4841aa3117b174e9ea468b1650e5ea 9 | with: 10 | build-path: test/build 11 | secrets: 12 | NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 13 | permissions: 14 | contents: write 15 | id-token: write 16 | attestations: write 17 | -------------------------------------------------------------------------------- /.github/workflows/test-js.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test-js: 7 | name: 'jsbt v0.3.3' # Should match commit below 8 | uses: paulmillr/jsbt/.github/workflows/test-js.yml@c9a9f2cd6b4841aa3117b174e9ea468b1650e5ea 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /test/build/ 3 | /lib 4 | /index.js 5 | /index.d.ts 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Paul Miller (https://paulmillr.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scure-base 2 | 3 | Audited & minimal implementation of bech32, base64, base58, base32 & base16. 4 | 5 | - 🔒 [Audited](#security) by an independent security firm 6 | - 🔻 Tree-shakeable: unused code is excluded from your builds 7 | - 📦 ESM and common.js 8 | - ✍️ Written in [functional style](#design-rationale), easily composable 9 | - 💼 Matches specs 10 | - [BIP173](https://en.bitcoin.it/wiki/BIP_0173), [BIP350](https://en.bitcoin.it/wiki/BIP_0350) for bech32 / bech32m 11 | - [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648) (aka RFC 3548) for Base16, Base32, Base32Hex, Base64, Base64Url 12 | - [Base58](https://www.ietf.org/archive/id/draft-msporny-base58-03.txt), 13 | [Base58check](https://en.bitcoin.it/wiki/Base58Check_encoding), 14 | [Base32 Crockford](https://www.crockford.com/base32.html) 15 | - 🪶 4KB gzipped 16 | 17 | Check out [Projects using scure-base](#projects-using-scure-base). 18 | 19 | ### This library belongs to _scure_ 20 | 21 | > **scure** — audited micro-libraries. 22 | 23 | - Zero or minimal dependencies 24 | - Highly readable TypeScript / JS code 25 | - PGP-signed releases and transparent NPM builds 26 | - Check out [homepage](https://paulmillr.com/noble/#scure) & all libraries: 27 | [base](https://github.com/paulmillr/scure-base), 28 | [bip32](https://github.com/paulmillr/scure-bip32), 29 | [bip39](https://github.com/paulmillr/scure-bip39), 30 | [btc-signer](https://github.com/paulmillr/scure-btc-signer), 31 | [starknet](https://github.com/paulmillr/scure-starknet) 32 | 33 | ## Usage 34 | 35 | > `npm install @scure/base` 36 | 37 | > `deno add jsr:@scure/base` 38 | 39 | > `deno doc jsr:@scure/base` # command-line documentation 40 | 41 | We support all major platforms and runtimes. The library is hybrid ESM / Common.js package. 42 | 43 | ```js 44 | import { base16, base32, base64, base58 } from '@scure/base'; 45 | // Flavors 46 | import { 47 | base58xmr, 48 | base58xrp, 49 | base32nopad, 50 | base32hex, 51 | base32hexnopad, 52 | base32crockford, 53 | base64nopad, 54 | base64url, 55 | base64urlnopad, 56 | } from '@scure/base'; 57 | 58 | const data = Uint8Array.from([1, 2, 3]); 59 | base64.decode(base64.encode(data)); 60 | 61 | // Convert utf8 string to Uint8Array 62 | const data2 = new TextEncoder().encode('hello'); 63 | base58.encode(data2); 64 | 65 | // Everything has the same API except for bech32 and base58check 66 | base32.encode(data); 67 | base16.encode(data); 68 | base32hex.encode(data); 69 | ``` 70 | 71 | base58check is a special case: you need to pass `sha256()` function: 72 | 73 | ```js 74 | import { createBase58check } from '@scure/base'; 75 | createBase58check(sha256).encode(data); 76 | ``` 77 | 78 | ## Bech32, Bech32m and Bitcoin 79 | 80 | We provide low-level bech32 operations. 81 | If you need high-level methods for BTC (addresses, and others), use 82 | [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer) instead. 83 | 84 | Bitcoin addresses use both 5-bit words and bytes representations. 85 | They can't be parsed using `bech32.decodeToBytes`. 86 | 87 | Same applies to Lightning Invoice Protocol 88 | [BOLT-11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md). 89 | We have many tests in `./test/bip173.test.js` that serve as minimal examples of 90 | Bitcoin address and Lightning Invoice Protocol parsers. 91 | Keep in mind that you'll need to verify the examples before using them in your code. 92 | 93 | Do something like this: 94 | 95 | ```ts 96 | const decoded = bech32.decode(address); 97 | // NOTE: words in bitcoin addresses contain version as first element, 98 | // with actual witness program words in rest 99 | // BIP-141: The value of the first push is called the "version byte". 100 | // The following byte vector pushed is called the "witness program". 101 | const [version, ...dataW] = decoded.words; 102 | const program = bech32.fromWords(dataW); // actual witness program 103 | ``` 104 | 105 | ## Design rationale 106 | 107 | The code may feel unnecessarily complicated; but actually it's much easier to reason about. 108 | Any encoding library consists of two functions: 109 | 110 | ``` 111 | encode(A) -> B 112 | decode(B) -> A 113 | where X = decode(encode(X)) 114 | # encode(decode(X)) can be !== X! 115 | # because decoding can normalize input 116 | 117 | e.g. 118 | base58checksum = { 119 | encode(): { 120 | // checksum 121 | // radix conversion 122 | // alphabet 123 | }, 124 | decode(): { 125 | // alphabet 126 | // radix conversion 127 | // checksum 128 | } 129 | } 130 | ``` 131 | 132 | But instead of creating two big functions for each specific case, 133 | we create them from tiny composable building blocks: 134 | 135 | ``` 136 | base58checksum = chain(checksum(), radix(), alphabet()) 137 | ``` 138 | 139 | Which is the same as chain/pipe/sequence function in Functional Programming, 140 | but significantly more useful since it enforces same order of execution of encode/decode. 141 | Basically you only define encode (in declarative way) and get correct decode for free. 142 | So, instead of reasoning about two big functions you need only reason about primitives and encode chain. 143 | The design revealed obvious bug in older version of the lib, 144 | where xmr version of base58 had errors in decode's block processing. 145 | 146 | Besides base-encodings, we can reuse the same approach with any encode/decode function 147 | (`bytes2number`, `bytes2u32`, etc). 148 | For example, you can easily encode entropy to mnemonic (BIP-39): 149 | 150 | ```ts 151 | export function getCoder(wordlist: string[]) { 152 | if (!Array.isArray(wordlist) || wordlist.length !== 2 ** 11 || typeof wordlist[0] !== 'string') { 153 | throw new Error('Wordlist: expected array of 2048 strings'); 154 | } 155 | return mbc.chain(mbu.checksum(1, checksum), mbu.radix2(11, true), mbu.alphabet(wordlist)); 156 | } 157 | ``` 158 | 159 | ### base58 is O(n^2) and radixes 160 | 161 | `Uint8Array` is represented as big-endian number: 162 | 163 | ``` 164 | [1, 2, 3, 4, 5] -> 1*(256**4) + 2*(256**3) 3*(256**2) + 4*(256**1) + 5*(256**0) 165 | where 256 = 2**8 (8 bits per byte) 166 | ``` 167 | 168 | which is then converted to a number in another radix/base (16/32/58/64, etc). 169 | 170 | However, generic conversion between bases has [quadratic O(n^2) time complexity](https://cs.stackexchange.com/q/21799). 171 | 172 | Which means base58 has quadratic time complexity too. Use base58 only when you have small 173 | constant sized input, because variable length sized input from user can cause DoS. 174 | 175 | On the other hand, if both bases are power of same number (like `2**8 <-> 2**64`), 176 | there is linear algorithm. For now we have implementation for power-of-two bases only (radix2). 177 | 178 | ## Security 179 | 180 | The library has been independently audited: 181 | 182 | - at version 1.0.0, in Jan 2022, by [cure53](https://cure53.de) 183 | - PDFs: [online](https://cure53.de/pentest-report_hashing-libs.pdf), [offline](./audit/2022-01-05-cure53-audit-nbl2.pdf) 184 | - [Changes since audit](https://github.com/paulmillr/scure-base/compare/1.0.0..main). 185 | - The audit has been funded by [Ethereum Foundation](https://ethereum.org/en/) with help of [Nomic Labs](https://nomiclabs.io) 186 | 187 | The library was initially developed for [js-ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography). 188 | At commit [ae00e6d7](https://github.com/ethereum/js-ethereum-cryptography/commit/ae00e6d7d24fb3c76a1c7fe10039f6ecd120b77e), 189 | it was extracted to a separate package called `micro-base`. 190 | After the audit we've decided to use `@scure` NPM namespace for security. 191 | 192 | ### Supply chain security 193 | 194 | - **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures 195 | - **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs 196 | - Use GitHub CLI to verify single-file builds: 197 | `gh attestation verify --owner paulmillr scure-base.js` 198 | - **Rare releasing** is followed to ensure less re-audit need for end-users 199 | - **Dependencies** are minimized and locked-down: any dependency could get hacked and users will be downloading malware with every install. 200 | - We make sure to use as few dependencies as possible 201 | - Automatic dep updates are prevented by locking-down version ranges; diffs are checked with `npm-diff` 202 | - **Dev Dependencies** are disabled for end-users; they are only used to develop / build the source code 203 | 204 | For this package, there are 0 dependencies; and a few dev dependencies: 205 | 206 | - micro-bmark, micro-should and jsbt are used for benchmarking / testing / build tooling and developed by the same author 207 | - prettier, fast-check and typescript are used for code quality / test generation / ts compilation. It's hard to audit their source code thoroughly and fully because of their size 208 | 209 | ## Contributing & testing 210 | 211 | - `npm install && npm run build && npm test` will build the code and run tests. 212 | - `npm run lint` / `npm run format` will run linter / fix linter issues. 213 | - `npm run build:release` will build single file 214 | 215 | ### Projects using scure-base 216 | 217 | - [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer) 218 | - [prefixed-api-key](https://github.com/truestamp/prefixed-api-key) 219 | - [coinspace](https://github.com/CoinSpace/CoinSpace) wallet and its modules: 220 | [ada](https://github.com/CoinSpace/cs-cardano-wallet), 221 | [btc](https://github.com/CoinSpace/cs-bitcoin-wallet) 222 | [eos](https://github.com/CoinSpace/cs-eos-wallet), 223 | [sol](https://github.com/CoinSpace/cs-solana-wallet), 224 | [xmr](https://github.com/CoinSpace/cs-monero-wallet) 225 | 226 | ## License 227 | 228 | MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file. 229 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | See [README's Security section](./README.md#security) for detailed description of internal security practices. 4 | 5 | ## Supported Versions 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | >=1.0.0 | :white_check_mark: | 10 | | <1.0.0 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Use maintainer's email specified at https://paulmillr.com 15 | 16 | It's preferred that you use 17 | PGP key from [pgp proof](https://paulmillr.com/pgp_proof.txt) (current is [697079DA6878B89B](https://paulmillr.com/pgp_proof.txt)). 18 | Ensure the pgp proof page has maintainer's site/github specified. 19 | 20 | You will get an update as soon as the email is read; a "Security vulnerability" phrase in email's title would help. 21 | -------------------------------------------------------------------------------- /audit/2022-01-05-cure53-audit-nbl2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmillr/scure-base/ffaad85318b400399965e8d73e1f936d78989308/audit/2022-01-05-cure53-audit-nbl2.pdf -------------------------------------------------------------------------------- /audit/README.md: -------------------------------------------------------------------------------- 1 | # Audit 2 | 3 | The PDF was saved from cure53.de site: [URL](https://cure53.de/pentest-report_hashing-libs.pdf). See information about audit in root [README](../README.md). 4 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */ 2 | 3 | export interface Coder { 4 | encode(from: F): T; 5 | decode(to: T): F; 6 | } 7 | 8 | export interface BytesCoder extends Coder { 9 | encode: (data: Uint8Array) => string; 10 | decode: (str: string) => Uint8Array; 11 | } 12 | 13 | function isBytes(a: unknown): a is Uint8Array { 14 | return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array'); 15 | } 16 | /** Asserts something is Uint8Array. */ 17 | function abytes(b: Uint8Array | undefined, ...lengths: number[]): void { 18 | if (!isBytes(b)) throw new Error('Uint8Array expected'); 19 | if (lengths.length > 0 && !lengths.includes(b.length)) 20 | throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length); 21 | } 22 | 23 | function isArrayOf(isString: boolean, arr: any[]) { 24 | if (!Array.isArray(arr)) return false; 25 | if (arr.length === 0) return true; 26 | if (isString) { 27 | return arr.every((item) => typeof item === 'string'); 28 | } else { 29 | return arr.every((item) => Number.isSafeInteger(item)); 30 | } 31 | } 32 | 33 | // no abytes: seems to have 10% slowdown. Why?! 34 | 35 | function afn(input: Function): input is Function { 36 | if (typeof input !== 'function') throw new Error('function expected'); 37 | return true; 38 | } 39 | 40 | function astr(label: string, input: unknown): input is string { 41 | if (typeof input !== 'string') throw new Error(`${label}: string expected`); 42 | return true; 43 | } 44 | 45 | function anumber(n: number): void { 46 | if (!Number.isSafeInteger(n)) throw new Error(`invalid integer: ${n}`); 47 | } 48 | 49 | function aArr(input: any[]) { 50 | if (!Array.isArray(input)) throw new Error('array expected'); 51 | } 52 | function astrArr(label: string, input: string[]) { 53 | if (!isArrayOf(true, input)) throw new Error(`${label}: array of strings expected`); 54 | } 55 | function anumArr(label: string, input: number[]) { 56 | if (!isArrayOf(false, input)) throw new Error(`${label}: array of numbers expected`); 57 | } 58 | 59 | // TODO: some recusive type inference so it would check correct order of input/output inside rest? 60 | // like , , 61 | type Chain = [Coder, ...Coder[]]; 62 | // Extract info from Coder type 63 | type Input = F extends Coder ? T : never; 64 | type Output = F extends Coder ? T : never; 65 | // Generic function for arrays 66 | type First = T extends [infer U, ...any[]] ? U : never; 67 | type Last = T extends [...any[], infer U] ? U : never; 68 | type Tail = T extends [any, ...infer U] ? U : never; 69 | 70 | type AsChain> = { 71 | // C[K] = Coder, Input> 72 | [K in keyof C]: Coder, Input>; 73 | }; 74 | 75 | /** 76 | * @__NO_SIDE_EFFECTS__ 77 | */ 78 | function chain>(...args: T): Coder>, Output>> { 79 | const id = (a: any) => a; 80 | // Wrap call in closure so JIT can inline calls 81 | const wrap = (a: any, b: any) => (c: any) => a(b(c)); 82 | // Construct chain of args[-1].encode(args[-2].encode([...])) 83 | const encode = args.map((x) => x.encode).reduceRight(wrap, id); 84 | // Construct chain of args[0].decode(args[1].decode(...)) 85 | const decode = args.map((x) => x.decode).reduce(wrap, id); 86 | return { encode, decode }; 87 | } 88 | 89 | /** 90 | * Encodes integer radix representation to array of strings using alphabet and back. 91 | * Could also be array of strings. 92 | * @__NO_SIDE_EFFECTS__ 93 | */ 94 | function alphabet(letters: string | string[]): Coder { 95 | // mapping 1 to "b" 96 | const lettersA = typeof letters === 'string' ? letters.split('') : letters; 97 | const len = lettersA.length; 98 | astrArr('alphabet', lettersA); 99 | 100 | // mapping "b" to 1 101 | const indexes = new Map(lettersA.map((l, i) => [l, i])); 102 | return { 103 | encode: (digits: number[]) => { 104 | aArr(digits); 105 | return digits.map((i) => { 106 | if (!Number.isSafeInteger(i) || i < 0 || i >= len) 107 | throw new Error( 108 | `alphabet.encode: digit index outside alphabet "${i}". Allowed: ${letters}` 109 | ); 110 | return lettersA[i]!; 111 | }); 112 | }, 113 | decode: (input: string[]): number[] => { 114 | aArr(input); 115 | return input.map((letter) => { 116 | astr('alphabet.decode', letter); 117 | const i = indexes.get(letter); 118 | if (i === undefined) throw new Error(`Unknown letter: "${letter}". Allowed: ${letters}`); 119 | return i; 120 | }); 121 | }, 122 | }; 123 | } 124 | 125 | /** 126 | * @__NO_SIDE_EFFECTS__ 127 | */ 128 | function join(separator = ''): Coder { 129 | astr('join', separator); 130 | return { 131 | encode: (from) => { 132 | astrArr('join.decode', from); 133 | return from.join(separator); 134 | }, 135 | decode: (to) => { 136 | astr('join.decode', to); 137 | return to.split(separator); 138 | }, 139 | }; 140 | } 141 | 142 | /** 143 | * Pad strings array so it has integer number of bits 144 | * @__NO_SIDE_EFFECTS__ 145 | */ 146 | function padding(bits: number, chr = '='): Coder { 147 | anumber(bits); 148 | astr('padding', chr); 149 | return { 150 | encode(data: string[]): string[] { 151 | astrArr('padding.encode', data); 152 | while ((data.length * bits) % 8) data.push(chr); 153 | return data; 154 | }, 155 | decode(input: string[]): string[] { 156 | astrArr('padding.decode', input); 157 | let end = input.length; 158 | if ((end * bits) % 8) 159 | throw new Error('padding: invalid, string should have whole number of bytes'); 160 | for (; end > 0 && input[end - 1] === chr; end--) { 161 | const last = end - 1; 162 | const byte = last * bits; 163 | if (byte % 8 === 0) throw new Error('padding: invalid, string has too much padding'); 164 | } 165 | return input.slice(0, end); 166 | }, 167 | }; 168 | } 169 | 170 | /** 171 | * @__NO_SIDE_EFFECTS__ 172 | */ 173 | function normalize(fn: (val: T) => T): Coder { 174 | afn(fn); 175 | return { encode: (from: T) => from, decode: (to: T) => fn(to) }; 176 | } 177 | 178 | /** 179 | * Slow: O(n^2) time complexity 180 | */ 181 | function convertRadix(data: number[], from: number, to: number): number[] { 182 | // base 1 is impossible 183 | if (from < 2) throw new Error(`convertRadix: invalid from=${from}, base cannot be less than 2`); 184 | if (to < 2) throw new Error(`convertRadix: invalid to=${to}, base cannot be less than 2`); 185 | aArr(data); 186 | if (!data.length) return []; 187 | let pos = 0; 188 | const res = []; 189 | const digits = Array.from(data, (d) => { 190 | anumber(d); 191 | if (d < 0 || d >= from) throw new Error(`invalid integer: ${d}`); 192 | return d; 193 | }); 194 | const dlen = digits.length; 195 | while (true) { 196 | let carry = 0; 197 | let done = true; 198 | for (let i = pos; i < dlen; i++) { 199 | const digit = digits[i]!; 200 | const fromCarry = from * carry; 201 | const digitBase = fromCarry + digit; 202 | if ( 203 | !Number.isSafeInteger(digitBase) || 204 | fromCarry / from !== carry || 205 | digitBase - digit !== fromCarry 206 | ) { 207 | throw new Error('convertRadix: carry overflow'); 208 | } 209 | const div = digitBase / to; 210 | carry = digitBase % to; 211 | const rounded = Math.floor(div); 212 | digits[i] = rounded; 213 | if (!Number.isSafeInteger(rounded) || rounded * to + carry !== digitBase) 214 | throw new Error('convertRadix: carry overflow'); 215 | if (!done) continue; 216 | else if (!rounded) pos = i; 217 | else done = false; 218 | } 219 | res.push(carry); 220 | if (done) break; 221 | } 222 | for (let i = 0; i < data.length - 1 && data[i] === 0; i++) res.push(0); 223 | return res.reverse(); 224 | } 225 | 226 | const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b)); 227 | const radix2carry = /* @__NO_SIDE_EFFECTS__ */ (from: number, to: number) => 228 | from + (to - gcd(from, to)); 229 | const powers: number[] = /* @__PURE__ */ (() => { 230 | let res = []; 231 | for (let i = 0; i < 40; i++) res.push(2 ** i); 232 | return res; 233 | })(); 234 | /** 235 | * Implemented with numbers, because BigInt is 5x slower 236 | */ 237 | function convertRadix2(data: number[], from: number, to: number, padding: boolean): number[] { 238 | aArr(data); 239 | if (from <= 0 || from > 32) throw new Error(`convertRadix2: wrong from=${from}`); 240 | if (to <= 0 || to > 32) throw new Error(`convertRadix2: wrong to=${to}`); 241 | if (radix2carry(from, to) > 32) { 242 | throw new Error( 243 | `convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}` 244 | ); 245 | } 246 | let carry = 0; 247 | let pos = 0; // bitwise position in current element 248 | const max = powers[from]!; 249 | const mask = powers[to]! - 1; 250 | const res: number[] = []; 251 | for (const n of data) { 252 | anumber(n); 253 | if (n >= max) throw new Error(`convertRadix2: invalid data word=${n} from=${from}`); 254 | carry = (carry << from) | n; 255 | if (pos + from > 32) throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`); 256 | pos += from; 257 | for (; pos >= to; pos -= to) res.push(((carry >> (pos - to)) & mask) >>> 0); 258 | const pow = powers[pos]; 259 | if (pow === undefined) throw new Error('invalid carry'); 260 | carry &= pow - 1; // clean carry, otherwise it will cause overflow 261 | } 262 | carry = (carry << (to - pos)) & mask; 263 | if (!padding && pos >= from) throw new Error('Excess padding'); 264 | if (!padding && carry > 0) throw new Error(`Non-zero padding: ${carry}`); 265 | if (padding && pos > 0) res.push(carry >>> 0); 266 | return res; 267 | } 268 | 269 | /** 270 | * @__NO_SIDE_EFFECTS__ 271 | */ 272 | function radix(num: number): Coder { 273 | anumber(num); 274 | const _256 = 2 ** 8; 275 | return { 276 | encode: (bytes: Uint8Array) => { 277 | if (!isBytes(bytes)) throw new Error('radix.encode input should be Uint8Array'); 278 | return convertRadix(Array.from(bytes), _256, num); 279 | }, 280 | decode: (digits: number[]) => { 281 | anumArr('radix.decode', digits); 282 | return Uint8Array.from(convertRadix(digits, num, _256)); 283 | }, 284 | }; 285 | } 286 | 287 | /** 288 | * If both bases are power of same number (like `2**8 <-> 2**64`), 289 | * there is a linear algorithm. For now we have implementation for power-of-two bases only. 290 | * @__NO_SIDE_EFFECTS__ 291 | */ 292 | function radix2(bits: number, revPadding = false): Coder { 293 | anumber(bits); 294 | if (bits <= 0 || bits > 32) throw new Error('radix2: bits should be in (0..32]'); 295 | if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32) 296 | throw new Error('radix2: carry overflow'); 297 | return { 298 | encode: (bytes: Uint8Array) => { 299 | if (!isBytes(bytes)) throw new Error('radix2.encode input should be Uint8Array'); 300 | return convertRadix2(Array.from(bytes), 8, bits, !revPadding); 301 | }, 302 | decode: (digits: number[]) => { 303 | anumArr('radix2.decode', digits); 304 | return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding)); 305 | }, 306 | }; 307 | } 308 | 309 | type ArgumentTypes = F extends (...args: infer A) => any ? A : never; 310 | function unsafeWrapper any>(fn: T) { 311 | afn(fn); 312 | return function (...args: ArgumentTypes): ReturnType | void { 313 | try { 314 | return fn.apply(null, args); 315 | } catch (e) {} 316 | }; 317 | } 318 | 319 | function checksum( 320 | len: number, 321 | fn: (data: Uint8Array) => Uint8Array 322 | ): Coder { 323 | anumber(len); 324 | afn(fn); 325 | return { 326 | encode(data: Uint8Array) { 327 | if (!isBytes(data)) throw new Error('checksum.encode: input should be Uint8Array'); 328 | const sum = fn(data).slice(0, len); 329 | const res = new Uint8Array(data.length + len); 330 | res.set(data); 331 | res.set(sum, data.length); 332 | return res; 333 | }, 334 | decode(data: Uint8Array) { 335 | if (!isBytes(data)) throw new Error('checksum.decode: input should be Uint8Array'); 336 | const payload = data.slice(0, -len); 337 | const oldChecksum = data.slice(-len); 338 | const newChecksum = fn(payload).slice(0, len); 339 | for (let i = 0; i < len; i++) 340 | if (newChecksum[i] !== oldChecksum[i]) throw new Error('Invalid checksum'); 341 | return payload; 342 | }, 343 | }; 344 | } 345 | 346 | // prettier-ignore 347 | export const utils: { alphabet: typeof alphabet; chain: typeof chain; checksum: typeof checksum; convertRadix: typeof convertRadix; convertRadix2: typeof convertRadix2; radix: typeof radix; radix2: typeof radix2; join: typeof join; padding: typeof padding; } = { 348 | alphabet, chain, checksum, convertRadix, convertRadix2, radix, radix2, join, padding, 349 | }; 350 | 351 | // RFC 4648 aka RFC 3548 352 | // --------------------- 353 | 354 | /** 355 | * base16 encoding from RFC 4648. 356 | * @example 357 | * ```js 358 | * base16.encode(Uint8Array.from([0x12, 0xab])); 359 | * // => '12AB' 360 | * ``` 361 | */ 362 | export const base16: BytesCoder = chain(radix2(4), alphabet('0123456789ABCDEF'), join('')); 363 | 364 | /** 365 | * base32 encoding from RFC 4648. Has padding. 366 | * Use `base32nopad` for unpadded version. 367 | * Also check out `base32hex`, `base32hexnopad`, `base32crockford`. 368 | * @example 369 | * ```js 370 | * base32.encode(Uint8Array.from([0x12, 0xab])); 371 | * // => 'CKVQ====' 372 | * base32.decode('CKVQ===='); 373 | * // => Uint8Array.from([0x12, 0xab]) 374 | * ``` 375 | */ 376 | export const base32: BytesCoder = chain( 377 | radix2(5), 378 | alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), 379 | padding(5), 380 | join('') 381 | ); 382 | 383 | /** 384 | * base32 encoding from RFC 4648. No padding. 385 | * Use `base32` for padded version. 386 | * Also check out `base32hex`, `base32hexnopad`, `base32crockford`. 387 | * @example 388 | * ```js 389 | * base32nopad.encode(Uint8Array.from([0x12, 0xab])); 390 | * // => 'CKVQ' 391 | * base32nopad.decode('CKVQ'); 392 | * // => Uint8Array.from([0x12, 0xab]) 393 | * ``` 394 | */ 395 | export const base32nopad: BytesCoder = chain( 396 | radix2(5), 397 | alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), 398 | join('') 399 | ); 400 | /** 401 | * base32 encoding from RFC 4648. Padded. Compared to ordinary `base32`, slightly different alphabet. 402 | * Use `base32hexnopad` for unpadded version. 403 | * @example 404 | * ```js 405 | * base32hex.encode(Uint8Array.from([0x12, 0xab])); 406 | * // => '2ALG====' 407 | * base32hex.decode('2ALG===='); 408 | * // => Uint8Array.from([0x12, 0xab]) 409 | * ``` 410 | */ 411 | export const base32hex: BytesCoder = chain( 412 | radix2(5), 413 | alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), 414 | padding(5), 415 | join('') 416 | ); 417 | 418 | /** 419 | * base32 encoding from RFC 4648. No padding. Compared to ordinary `base32`, slightly different alphabet. 420 | * Use `base32hex` for padded version. 421 | * @example 422 | * ```js 423 | * base32hexnopad.encode(Uint8Array.from([0x12, 0xab])); 424 | * // => '2ALG' 425 | * base32hexnopad.decode('2ALG'); 426 | * // => Uint8Array.from([0x12, 0xab]) 427 | * ``` 428 | */ 429 | export const base32hexnopad: BytesCoder = chain( 430 | radix2(5), 431 | alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), 432 | join('') 433 | ); 434 | /** 435 | * base32 encoding from RFC 4648. Doug Crockford's version. 436 | * https://www.crockford.com/base32.html 437 | * @example 438 | * ```js 439 | * base32crockford.encode(Uint8Array.from([0x12, 0xab])); 440 | * // => '2ANG' 441 | * base32crockford.decode('2ANG'); 442 | * // => Uint8Array.from([0x12, 0xab]) 443 | * ``` 444 | */ 445 | export const base32crockford: BytesCoder = chain( 446 | radix2(5), 447 | alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), 448 | join(''), 449 | normalize((s: string) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')) 450 | ); 451 | 452 | // Built-in base64 conversion https://caniuse.com/mdn-javascript_builtins_uint8array_frombase64 453 | // prettier-ignore 454 | const hasBase64Builtin: boolean = /* @__PURE__ */ (() => 455 | typeof (Uint8Array as any).from([]).toBase64 === 'function' && 456 | typeof (Uint8Array as any).fromBase64 === 'function')(); 457 | 458 | const decodeBase64Builtin = (s: string, isUrl: boolean) => { 459 | astr('base64', s); 460 | const re = isUrl ? /^[A-Za-z0-9=_-]+$/ : /^[A-Za-z0-9=+/]+$/; 461 | const alphabet = isUrl ? 'base64url' : 'base64'; 462 | if (s.length > 0 && !re.test(s)) throw new Error('invalid base64'); 463 | return (Uint8Array as any).fromBase64(s, { alphabet, lastChunkHandling: 'strict' }); 464 | }; 465 | 466 | /** 467 | * base64 from RFC 4648. Padded. 468 | * Use `base64nopad` for unpadded version. 469 | * Also check out `base64url`, `base64urlnopad`. 470 | * Falls back to built-in function, when available. 471 | * @example 472 | * ```js 473 | * base64.encode(Uint8Array.from([0x12, 0xab])); 474 | * // => 'Eqs=' 475 | * base64.decode('Eqs='); 476 | * // => Uint8Array.from([0x12, 0xab]) 477 | * ``` 478 | */ 479 | // prettier-ignore 480 | export const base64: BytesCoder = hasBase64Builtin ? { 481 | encode(b) { abytes(b); return (b as any).toBase64(); }, 482 | decode(s) { return decodeBase64Builtin(s, false); }, 483 | } : chain( 484 | radix2(6), 485 | alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'), 486 | padding(6), 487 | join('') 488 | ); 489 | /** 490 | * base64 from RFC 4648. No padding. 491 | * Use `base64` for padded version. 492 | * @example 493 | * ```js 494 | * base64nopad.encode(Uint8Array.from([0x12, 0xab])); 495 | * // => 'Eqs' 496 | * base64nopad.decode('Eqs'); 497 | * // => Uint8Array.from([0x12, 0xab]) 498 | * ``` 499 | */ 500 | export const base64nopad: BytesCoder = chain( 501 | radix2(6), 502 | alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'), 503 | join('') 504 | ); 505 | 506 | /** 507 | * base64 from RFC 4648, using URL-safe alphabet. Padded. 508 | * Use `base64urlnopad` for unpadded version. 509 | * Falls back to built-in function, when available. 510 | * @example 511 | * ```js 512 | * base64url.encode(Uint8Array.from([0x12, 0xab])); 513 | * // => 'Eqs=' 514 | * base64url.decode('Eqs='); 515 | * // => Uint8Array.from([0x12, 0xab]) 516 | * ``` 517 | */ 518 | // prettier-ignore 519 | export const base64url: BytesCoder = hasBase64Builtin ? { 520 | encode(b) { abytes(b); return (b as any).toBase64({ alphabet: 'base64url' }); }, 521 | decode(s) { return decodeBase64Builtin(s, true); }, 522 | } : chain( 523 | radix2(6), 524 | alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'), 525 | padding(6), 526 | join('') 527 | ); 528 | 529 | /** 530 | * base64 from RFC 4648, using URL-safe alphabet. No padding. 531 | * Use `base64url` for padded version. 532 | * @example 533 | * ```js 534 | * base64urlnopad.encode(Uint8Array.from([0x12, 0xab])); 535 | * // => 'Eqs' 536 | * base64urlnopad.decode('Eqs'); 537 | * // => Uint8Array.from([0x12, 0xab]) 538 | * ``` 539 | */ 540 | export const base64urlnopad: BytesCoder = chain( 541 | radix2(6), 542 | alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'), 543 | join('') 544 | ); 545 | 546 | // base58 code 547 | // ----------- 548 | const genBase58 = /* @__NO_SIDE_EFFECTS__ */ (abc: string) => 549 | chain(radix(58), alphabet(abc), join('')); 550 | 551 | /** 552 | * base58: base64 without ambigous characters +, /, 0, O, I, l. 553 | * Quadratic (O(n^2)) - so, can't be used on large inputs. 554 | * @example 555 | * ```js 556 | * base58.decode('01abcdef'); 557 | * // => '3UhJW' 558 | * ``` 559 | */ 560 | export const base58: BytesCoder = genBase58( 561 | '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 562 | ); 563 | /** 564 | * base58: flickr version. Check out `base58`. 565 | */ 566 | export const base58flickr: BytesCoder = genBase58( 567 | '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' 568 | ); 569 | /** 570 | * base58: XRP version. Check out `base58`. 571 | */ 572 | export const base58xrp: BytesCoder = genBase58( 573 | 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz' 574 | ); 575 | 576 | // Data len (index) -> encoded block len 577 | const XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11]; 578 | 579 | /** 580 | * base58: XMR version. Check out `base58`. 581 | * Done in 8-byte blocks (which equals 11 chars in decoding). Last (non-full) block padded with '1' to size in XMR_BLOCK_LEN. 582 | * Block encoding significantly reduces quadratic complexity of base58. 583 | */ 584 | export const base58xmr: BytesCoder = { 585 | encode(data: Uint8Array) { 586 | let res = ''; 587 | for (let i = 0; i < data.length; i += 8) { 588 | const block = data.subarray(i, i + 8); 589 | res += base58.encode(block).padStart(XMR_BLOCK_LEN[block.length]!, '1'); 590 | } 591 | return res; 592 | }, 593 | decode(str: string) { 594 | let res: number[] = []; 595 | for (let i = 0; i < str.length; i += 11) { 596 | const slice = str.slice(i, i + 11); 597 | const blockLen = XMR_BLOCK_LEN.indexOf(slice.length); 598 | const block = base58.decode(slice); 599 | for (let j = 0; j < block.length - blockLen; j++) { 600 | if (block[j] !== 0) throw new Error('base58xmr: wrong padding'); 601 | } 602 | res = res.concat(Array.from(block.slice(block.length - blockLen))); 603 | } 604 | return Uint8Array.from(res); 605 | }, 606 | }; 607 | 608 | /** 609 | * Method, which creates base58check encoder. 610 | * Requires function, calculating sha256. 611 | */ 612 | export const createBase58check = (sha256: (data: Uint8Array) => Uint8Array): BytesCoder => 613 | chain( 614 | checksum(4, (data) => sha256(sha256(data))), 615 | base58 616 | ); 617 | 618 | /** 619 | * Use `createBase58check` instead. 620 | * @deprecated 621 | */ 622 | export const base58check: (sha256: (data: Uint8Array) => Uint8Array) => BytesCoder = 623 | createBase58check; 624 | 625 | // Bech32 code 626 | // ----------- 627 | export interface Bech32Decoded { 628 | prefix: Prefix; 629 | words: number[]; 630 | } 631 | export interface Bech32DecodedWithArray { 632 | prefix: Prefix; 633 | words: number[]; 634 | bytes: Uint8Array; 635 | } 636 | 637 | const BECH_ALPHABET: Coder = chain( 638 | alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'), 639 | join('') 640 | ); 641 | 642 | const POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; 643 | function bech32Polymod(pre: number): number { 644 | const b = pre >> 25; 645 | let chk = (pre & 0x1ffffff) << 5; 646 | for (let i = 0; i < POLYMOD_GENERATORS.length; i++) { 647 | if (((b >> i) & 1) === 1) chk ^= POLYMOD_GENERATORS[i]!; 648 | } 649 | return chk; 650 | } 651 | 652 | function bechChecksum(prefix: string, words: number[], encodingConst = 1): string { 653 | const len = prefix.length; 654 | let chk = 1; 655 | for (let i = 0; i < len; i++) { 656 | const c = prefix.charCodeAt(i); 657 | if (c < 33 || c > 126) throw new Error(`Invalid prefix (${prefix})`); 658 | chk = bech32Polymod(chk) ^ (c >> 5); 659 | } 660 | chk = bech32Polymod(chk); 661 | for (let i = 0; i < len; i++) chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f); 662 | for (let v of words) chk = bech32Polymod(chk) ^ v; 663 | for (let i = 0; i < 6; i++) chk = bech32Polymod(chk); 664 | chk ^= encodingConst; 665 | return BECH_ALPHABET.encode(convertRadix2([chk % powers[30]!], 30, 5, false)); 666 | } 667 | 668 | export interface Bech32 { 669 | encode( 670 | prefix: Prefix, 671 | words: number[] | Uint8Array, 672 | limit?: number | false 673 | ): `${Lowercase}1${string}`; 674 | decode( 675 | str: `${Prefix}1${string}`, 676 | limit?: number | false 677 | ): Bech32Decoded; 678 | encodeFromBytes(prefix: string, bytes: Uint8Array): string; 679 | decodeToBytes(str: string): Bech32DecodedWithArray; 680 | decodeUnsafe(str: string, limit?: number | false): void | Bech32Decoded; 681 | fromWords(to: number[]): Uint8Array; 682 | fromWordsUnsafe(to: number[]): void | Uint8Array; 683 | toWords(from: Uint8Array): number[]; 684 | } 685 | /** 686 | * @__NO_SIDE_EFFECTS__ 687 | */ 688 | function genBech32(encoding: 'bech32' | 'bech32m'): Bech32 { 689 | const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3; 690 | const _words = radix2(5); 691 | const fromWords = _words.decode; 692 | const toWords = _words.encode; 693 | const fromWordsUnsafe = unsafeWrapper(fromWords); 694 | 695 | function encode( 696 | prefix: Prefix, 697 | words: number[] | Uint8Array, 698 | limit: number | false = 90 699 | ): `${Lowercase}1${string}` { 700 | astr('bech32.encode prefix', prefix); 701 | if (isBytes(words)) words = Array.from(words); 702 | anumArr('bech32.encode', words); 703 | const plen = prefix.length; 704 | if (plen === 0) throw new TypeError(`Invalid prefix length ${plen}`); 705 | const actualLength = plen + 7 + words.length; 706 | if (limit !== false && actualLength > limit) 707 | throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`); 708 | const lowered = prefix.toLowerCase(); 709 | const sum = bechChecksum(lowered, words, ENCODING_CONST); 710 | return `${lowered}1${BECH_ALPHABET.encode(words)}${sum}` as `${Lowercase}1${string}`; 711 | } 712 | 713 | function decode( 714 | str: `${Prefix}1${string}`, 715 | limit?: number | false 716 | ): Bech32Decoded; 717 | function decode(str: string, limit?: number | false): Bech32Decoded; 718 | function decode(str: string, limit: number | false = 90): Bech32Decoded { 719 | astr('bech32.decode input', str); 720 | const slen = str.length; 721 | if (slen < 8 || (limit !== false && slen > limit)) 722 | throw new TypeError(`invalid string length: ${slen} (${str}). Expected (8..${limit})`); 723 | // don't allow mixed case 724 | const lowered = str.toLowerCase(); 725 | if (str !== lowered && str !== str.toUpperCase()) 726 | throw new Error(`String must be lowercase or uppercase`); 727 | const sepIndex = lowered.lastIndexOf('1'); 728 | if (sepIndex === 0 || sepIndex === -1) 729 | throw new Error(`Letter "1" must be present between prefix and data only`); 730 | const prefix = lowered.slice(0, sepIndex); 731 | const data = lowered.slice(sepIndex + 1); 732 | if (data.length < 6) throw new Error('Data must be at least 6 characters long'); 733 | const words = BECH_ALPHABET.decode(data).slice(0, -6); 734 | const sum = bechChecksum(prefix, words, ENCODING_CONST); 735 | if (!data.endsWith(sum)) throw new Error(`Invalid checksum in ${str}: expected "${sum}"`); 736 | return { prefix, words }; 737 | } 738 | 739 | const decodeUnsafe = unsafeWrapper(decode); 740 | 741 | function decodeToBytes(str: string): Bech32DecodedWithArray { 742 | const { prefix, words } = decode(str, false); 743 | return { prefix, words, bytes: fromWords(words) }; 744 | } 745 | 746 | function encodeFromBytes(prefix: string, bytes: Uint8Array) { 747 | return encode(prefix, toWords(bytes)); 748 | } 749 | 750 | return { 751 | encode, 752 | decode, 753 | encodeFromBytes, 754 | decodeToBytes, 755 | decodeUnsafe, 756 | fromWords, 757 | fromWordsUnsafe, 758 | toWords, 759 | }; 760 | } 761 | 762 | /** 763 | * bech32 from BIP 173. Operates on words. 764 | * For high-level, check out scure-btc-signer: 765 | * https://github.com/paulmillr/scure-btc-signer. 766 | */ 767 | export const bech32: Bech32 = genBech32('bech32'); 768 | 769 | /** 770 | * bech32m from BIP 350. Operates on words. 771 | * It was to mitigate `bech32` weaknesses. 772 | * For high-level, check out scure-btc-signer: 773 | * https://github.com/paulmillr/scure-btc-signer. 774 | */ 775 | export const bech32m: Bech32 = genBech32('bech32m'); 776 | 777 | declare const TextEncoder: any; 778 | declare const TextDecoder: any; 779 | 780 | /** 781 | * UTF-8-to-byte decoder. Uses built-in TextDecoder / TextEncoder. 782 | * @example 783 | * ```js 784 | * const b = utf8.decode("hey"); // => new Uint8Array([ 104, 101, 121 ]) 785 | * const str = utf8.encode(b); // "hey" 786 | * ``` 787 | */ 788 | export const utf8: BytesCoder = { 789 | encode: (data) => new TextDecoder().decode(data), 790 | decode: (str) => new TextEncoder().encode(str), 791 | }; 792 | 793 | // Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex 794 | // prettier-ignore 795 | const hasHexBuiltin: boolean = /* @__PURE__ */ (() => 796 | typeof (Uint8Array as any).from([]).toHex === 'function' && 797 | typeof (Uint8Array as any).fromHex === 'function')(); 798 | // prettier-ignore 799 | const hexBuiltin: BytesCoder = { 800 | encode(data) { abytes(data); return (data as any).toHex(); }, 801 | decode(s) { astr('hex', s); return (Uint8Array as any).fromHex(s); }, 802 | }; 803 | /** 804 | * hex string decoder. Uses built-in function, when available. 805 | * @example 806 | * ```js 807 | * const b = hex.decode("0102ff"); // => new Uint8Array([ 1, 2, 255 ]) 808 | * const str = hex.encode(b); // "0102ff" 809 | * ``` 810 | */ 811 | export const hex: BytesCoder = hasHexBuiltin 812 | ? hexBuiltin 813 | : chain( 814 | radix2(4), 815 | alphabet('0123456789abcdef'), 816 | join(''), 817 | normalize((s: string) => { 818 | if (typeof s !== 'string' || s.length % 2 !== 0) 819 | throw new TypeError( 820 | `hex.decode: expected string, got ${typeof s} with length ${s.length}` 821 | ); 822 | return s.toLowerCase(); 823 | }) 824 | ); 825 | 826 | export type SomeCoders = { 827 | utf8: BytesCoder; 828 | hex: BytesCoder; 829 | base16: BytesCoder; 830 | base32: BytesCoder; 831 | base64: BytesCoder; 832 | base64url: BytesCoder; 833 | base58: BytesCoder; 834 | base58xmr: BytesCoder; 835 | }; 836 | // prettier-ignore 837 | const CODERS: SomeCoders = { 838 | utf8, hex, base16, base32, base64, base64url, base58, base58xmr 839 | }; 840 | type CoderType = keyof SomeCoders; 841 | const coderTypeError = 842 | 'Invalid encoding type. Available types: utf8, hex, base16, base32, base64, base64url, base58, base58xmr'; 843 | 844 | /** @deprecated */ 845 | export const bytesToString = (type: CoderType, bytes: Uint8Array): string => { 846 | if (typeof type !== 'string' || !CODERS.hasOwnProperty(type)) throw new TypeError(coderTypeError); 847 | if (!isBytes(bytes)) throw new TypeError('bytesToString() expects Uint8Array'); 848 | return CODERS[type].encode(bytes); 849 | }; 850 | 851 | /** @deprecated */ 852 | export const str: (type: CoderType, bytes: Uint8Array) => string = bytesToString; // as in python, but for bytes only 853 | 854 | /** @deprecated */ 855 | export const stringToBytes = (type: CoderType, str: string): Uint8Array => { 856 | if (!CODERS.hasOwnProperty(type)) throw new TypeError(coderTypeError); 857 | if (typeof str !== 'string') throw new TypeError('stringToBytes() expects string'); 858 | return CODERS[type].decode(str); 859 | }; 860 | /** @deprecated */ 861 | export const bytes: (type: CoderType, str: string) => Uint8Array = stringToBytes; 862 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scure/base", 3 | "version": "1.2.6", 4 | "exports": "./index.ts", 5 | "publish": { 6 | "include": [ 7 | "index.ts", 8 | "LICENSE", 9 | "README.md", 10 | "jsr.json" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/esm/package.json: -------------------------------------------------------------------------------- 1 | { "type": "module", "sideEffects": false } 2 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scure/base", 3 | "version": "1.2.6", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@scure/base", 9 | "version": "1.2.6", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@noble/hashes": "1.8.0", 13 | "@paulmillr/jsbt": "0.3.3", 14 | "@types/node": "22.15.23", 15 | "fast-check": "4.1.1", 16 | "micro-bmark": "0.4.2", 17 | "micro-should": "0.5.3", 18 | "prettier": "3.5.3", 19 | "typescript": "5.8.3" 20 | }, 21 | "funding": { 22 | "url": "https://paulmillr.com/funding/" 23 | } 24 | }, 25 | "node_modules/@noble/hashes": { 26 | "version": "1.8.0", 27 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", 28 | "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", 29 | "dev": true, 30 | "license": "MIT", 31 | "engines": { 32 | "node": "^14.21.3 || >=16" 33 | }, 34 | "funding": { 35 | "url": "https://paulmillr.com/funding/" 36 | } 37 | }, 38 | "node_modules/@paulmillr/jsbt": { 39 | "version": "0.3.3", 40 | "resolved": "https://registry.npmjs.org/@paulmillr/jsbt/-/jsbt-0.3.3.tgz", 41 | "integrity": "sha512-92Z78xwPjN1jjY4AHkaLWGuSk4ILQGJ2bEz0MzGJ6bYvl5dFHQMZR8irW7Myz+xpSbsST7d4ykjLPPaghI2apg==", 42 | "dev": true, 43 | "license": "MIT", 44 | "bin": { 45 | "jsbt": "jsbt.js" 46 | } 47 | }, 48 | "node_modules/@types/node": { 49 | "version": "22.15.23", 50 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.23.tgz", 51 | "integrity": "sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==", 52 | "dev": true, 53 | "license": "MIT", 54 | "dependencies": { 55 | "undici-types": "~6.21.0" 56 | } 57 | }, 58 | "node_modules/fast-check": { 59 | "version": "4.1.1", 60 | "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.1.1.tgz", 61 | "integrity": "sha512-8+yQYeNYqBfWem0Nmm7BUnh27wm+qwGvI0xln60c8RPM5rVekxZf/Ildng2GNBfjaG6utIebFmVBPlNtZlBLxg==", 62 | "dev": true, 63 | "funding": [ 64 | { 65 | "type": "individual", 66 | "url": "https://github.com/sponsors/dubzzz" 67 | }, 68 | { 69 | "type": "opencollective", 70 | "url": "https://opencollective.com/fast-check" 71 | } 72 | ], 73 | "license": "MIT", 74 | "dependencies": { 75 | "pure-rand": "^7.0.0" 76 | }, 77 | "engines": { 78 | "node": ">=12.17.0" 79 | } 80 | }, 81 | "node_modules/micro-bmark": { 82 | "version": "0.4.2", 83 | "resolved": "https://registry.npmjs.org/micro-bmark/-/micro-bmark-0.4.2.tgz", 84 | "integrity": "sha512-0M16Yxj6F1t0ZJoCT7xmHuM3AWm2d4NafAZamJFV++ZDD8uMtRMk7Nsq23LjHCSy246AqIBOLDGfeIoJBpipvg==", 85 | "dev": true, 86 | "license": "MIT" 87 | }, 88 | "node_modules/micro-should": { 89 | "version": "0.5.3", 90 | "resolved": "https://registry.npmjs.org/micro-should/-/micro-should-0.5.3.tgz", 91 | "integrity": "sha512-3gEuTzROE856pZSijMD5NonIrQTEGLdkMKj42S2JCqpXiaqQdoSqEd/mTonelAT0ZNwheY7pA/w6eAotQTXeWQ==", 92 | "dev": true, 93 | "license": "MIT" 94 | }, 95 | "node_modules/prettier": { 96 | "version": "3.5.3", 97 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", 98 | "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", 99 | "dev": true, 100 | "license": "MIT", 101 | "bin": { 102 | "prettier": "bin/prettier.cjs" 103 | }, 104 | "engines": { 105 | "node": ">=14" 106 | }, 107 | "funding": { 108 | "url": "https://github.com/prettier/prettier?sponsor=1" 109 | } 110 | }, 111 | "node_modules/pure-rand": { 112 | "version": "7.0.1", 113 | "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", 114 | "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", 115 | "dev": true, 116 | "funding": [ 117 | { 118 | "type": "individual", 119 | "url": "https://github.com/sponsors/dubzzz" 120 | }, 121 | { 122 | "type": "opencollective", 123 | "url": "https://opencollective.com/fast-check" 124 | } 125 | ], 126 | "license": "MIT" 127 | }, 128 | "node_modules/typescript": { 129 | "version": "5.8.3", 130 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 131 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 132 | "dev": true, 133 | "license": "Apache-2.0", 134 | "bin": { 135 | "tsc": "bin/tsc", 136 | "tsserver": "bin/tsserver" 137 | }, 138 | "engines": { 139 | "node": ">=14.17" 140 | } 141 | }, 142 | "node_modules/undici-types": { 143 | "version": "6.21.0", 144 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 145 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 146 | "dev": true, 147 | "license": "MIT" 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scure/base", 3 | "version": "1.2.6", 4 | "description": "Secure, audited & 0-dep implementation of base64, bech32, base58, base32 & base16", 5 | "files": [ 6 | "lib/index.js", 7 | "lib/index.js.map", 8 | "lib/index.d.ts", 9 | "lib/index.d.ts.map", 10 | "lib/esm/index.js", 11 | "lib/esm/index.js.map", 12 | "lib/esm/index.d.ts", 13 | "lib/esm/index.d.ts.map", 14 | "lib/esm/package.json", 15 | "index.ts" 16 | ], 17 | "main": "./lib/index.js", 18 | "module": "./lib/esm/index.js", 19 | "types": "./lib/index.d.ts", 20 | "exports": { 21 | ".": { 22 | "import": "./lib/esm/index.js", 23 | "require": "./lib/index.js" 24 | } 25 | }, 26 | "scripts": { 27 | "bench": "node test/benchmark/index.js", 28 | "build": "tsc && tsc -p tsconfig.cjs.json", 29 | "build:release": "npx jsbt esbuild test/build", 30 | "lint": "prettier --check index.ts", 31 | "format": "prettier --write index.ts", 32 | "test": "node test/index.js", 33 | "test:bun": "bun test/index.js", 34 | "test:deno": "deno --allow-env --allow-read test/index.js && deno test/deno.ts" 35 | }, 36 | "sideEffects": false, 37 | "author": "Paul Miller (https://paulmillr.com)", 38 | "license": "MIT", 39 | "homepage": "https://paulmillr.com/noble/#scure", 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/paulmillr/scure-base.git" 43 | }, 44 | "devDependencies": { 45 | "@noble/hashes": "1.8.0", 46 | "@paulmillr/jsbt": "0.3.3", 47 | "@types/node": "22.15.23", 48 | "fast-check": "4.1.1", 49 | "micro-bmark": "0.4.2", 50 | "micro-should": "0.5.3", 51 | "prettier": "3.5.3", 52 | "typescript": "5.8.3" 53 | }, 54 | "keywords": [ 55 | "bech32", 56 | "bech32m", 57 | "base64", 58 | "base58", 59 | "base32", 60 | "base16", 61 | "rfc4648", 62 | "rfc3548", 63 | "crockford", 64 | "encode", 65 | "encoder", 66 | "base-x", 67 | "base" 68 | ], 69 | "funding": "https://paulmillr.com/funding/" 70 | } 71 | -------------------------------------------------------------------------------- /test/base58.test.js: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha2'; 2 | import { describe, should } from 'micro-should'; 3 | import { deepStrictEqual as eql, throws } from 'node:assert'; 4 | import { Buffer } from 'node:buffer'; 5 | import { base58, base58xmr, base58xrp, createBase58check } from '../lib/esm/index.js'; 6 | import { json } from './utils.js'; 7 | 8 | const VECTORS_2 = json('./vectors/base58.json'); 9 | // https://github.com/bigreddmachine/MoneroPy/blob/master/tests/testdata.py (BSD license) 10 | const XMR_VECTORS = json('./vectors/base58_xmr.json'); 11 | // https://github.com/bitcoinjs/bs58check/blob/master/test/fixtures.json (MIT license) 12 | const B58CHK_VECTORS = json('./vectors/base58_check.json'); 13 | 14 | const hexToArray = (hex) => Uint8Array.from(Buffer.from(hex, 'hex')); 15 | const asciiToArray = (str) => new Uint8Array(str.split('').map((c) => c.charCodeAt(0))); 16 | 17 | const VECTORS_1 = [ 18 | { decoded: asciiToArray('hello world'), encoded: 'StV1DL6CwTryKyV' }, 19 | { decoded: Uint8Array.from(Buffer.from('hello world')), encoded: 'StV1DL6CwTryKyV' }, 20 | { decoded: asciiToArray('hello world'), encoded: 'StV1DL6CwTryKyV' }, 21 | { decoded: asciiToArray('hello world'), encoded: 'StVrDLaUATiyKyV', isXRP: true }, 22 | { decoded: asciiToArray('\0\0hello world'), encoded: '11StV1DL6CwTryKyV' }, 23 | { decoded: asciiToArray(''), encoded: '' }, 24 | { decoded: Uint8Array.from(Buffer.from([0x51, 0x6b, 0x6f, 0xcd, 0x0f])), encoded: 'ABnLTmg' }, 25 | { decoded: new Uint8Array([0x51, 0x6b, 0x6f, 0xcd, 0x0f]), encoded: 'ABnLTmg' }, 26 | { decoded: asciiToArray('Hello World!'), encoded: '2NEpo7TZRRrLZSi2U' }, 27 | { 28 | decoded: asciiToArray('The quick brown fox jumps over the lazy dog.'), 29 | encoded: 'USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z', 30 | }, 31 | { decoded: new Uint8Array([0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd]), encoded: '11233QC4' }, 32 | ]; 33 | 34 | should('base58: vectors1', () => { 35 | for (const vector of VECTORS_1) { 36 | const dec = vector.decoded; 37 | const vectorDecodedArr = typeof dec === 'string' ? asciiToArray(dec) : dec; 38 | const coder = vector.isXRP ? base58xrp : base58; 39 | 40 | const encoded = coder.encode(vector.decoded); 41 | eql(encoded, vector.encoded); 42 | eql(coder.decode(encoded), vectorDecodedArr); 43 | } 44 | }); 45 | 46 | should('base58: vectors2', () => { 47 | for (const { decodedHex, encoded } of VECTORS_2) { 48 | const txt = hexToArray(decodedHex); 49 | eql(base58.encode(txt), encoded); 50 | } 51 | }); 52 | 53 | describe('base58: xmr vectors', () => { 54 | for (let i = 0; i < XMR_VECTORS.validAddrs.length; i++) { 55 | should(`${i}`, () => { 56 | const decAddr = XMR_VECTORS.decodedAddrs[i]; 57 | const validAddr = XMR_VECTORS.validAddrs[i]; 58 | eql(base58xmr.encode(hexToArray(decAddr)), validAddr, 'encode'); 59 | eql(base58xmr.decode(validAddr), hexToArray(decAddr), 'decode'); 60 | }); 61 | } 62 | }); 63 | 64 | const base58check = createBase58check(sha256); 65 | 66 | for (const v of B58CHK_VECTORS.valid) { 67 | should(`b58-check: decode ${v}`, () => { 68 | const actual = base58check.decode(v.string); 69 | eql(Buffer.from(actual).toString('hex'), v.payload); 70 | }); 71 | should(`b58-check: decode ${v}`, () => { 72 | eql(base58check.encode(Buffer.from(v.payload, 'hex')), v.string); 73 | }); 74 | } 75 | for (const v of B58CHK_VECTORS.invalid) { 76 | should(`b58-check: decode throws on ${v.exception}`, () => { 77 | throws(() => base58check.decode(v.string)); 78 | }); 79 | } 80 | 81 | should('base58xmr: wrong blockLen', () => { 82 | const vectors = [ 83 | '1', 84 | 'z', 85 | '1111', 86 | 'zzzz', 87 | '11111111', 88 | 'zzzzzzzz', 89 | '123456789AB1', 90 | '123456789ABz', 91 | '123456789AB1111', 92 | '123456789ABzzzz', 93 | '123456789AB11111111', 94 | '123456789ABzzzzzzzz', 95 | ]; 96 | for (const v of vectors) throws(() => base58xmr.decode(v)); 97 | }); 98 | 99 | should('base58xmr: wrong base', () => { 100 | const vectors = [ 101 | '5R', 102 | 'zz', 103 | 'LUw', 104 | 'zzz', 105 | '2UzHM', 106 | 'zzzzz', 107 | '7YXq9H', 108 | 'zzzzzz', 109 | 'VtB5VXd', 110 | 'zzzzzzz', 111 | '3CUsUpv9u', 112 | 'zzzzzzzzz', 113 | 'Ahg1opVcGX', 114 | 'zzzzzzzzzz', 115 | 'jpXCZedGfVR', 116 | 'zzzzzzzzzzz', 117 | '123456789AB5R', 118 | '123456789ABzz', 119 | '123456789ABLUw', 120 | '123456789ABzzz', 121 | '123456789AB2UzHM', 122 | '123456789ABzzzzz', 123 | '123456789AB7YXq9H', 124 | '123456789ABzzzzzz', 125 | '123456789ABVtB5VXd', 126 | '123456789ABzzzzzzz', 127 | '123456789AB3CUsUpv9u', 128 | '123456789ABzzzzzzzzz', 129 | '123456789ABAhg1opVcGX', 130 | '123456789ABzzzzzzzzzz', 131 | '123456789ABjpXCZedGfVR', 132 | '123456789ABzzzzzzzzzzz', 133 | 'zzzzzzzzzzz11', 134 | ]; 135 | for (const v of vectors) throws(() => base58xmr.decode(v)); 136 | }); 137 | 138 | should('base58xmr: wrong chars', () => { 139 | const vectors = [ 140 | '10', 141 | '11I', 142 | '11O11', 143 | '11l111', 144 | '11_11111111', 145 | '1101111111111', 146 | '11I11111111111111', 147 | '11O1111111111111111111', 148 | '1111111111110', 149 | '111111111111l1111', 150 | '111111111111_111111111', 151 | ]; 152 | for (const v of vectors) throws(() => base58xmr.decode(v)); 153 | }); 154 | 155 | should.runWhen(import.meta.url); 156 | -------------------------------------------------------------------------------- /test/bases.test.js: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha2.js'; 2 | import { should } from 'micro-should'; 3 | import { deepStrictEqual as eql, throws } from 'node:assert'; 4 | import { Buffer } from 'node:buffer'; 5 | import { 6 | base32, 7 | base32crockford, 8 | base32hex, 9 | base32hexnopad, 10 | base32nopad, 11 | base58, 12 | base58xmr, 13 | base58xrp, 14 | base64, 15 | base64nopad, 16 | base64url, 17 | base64urlnopad, 18 | bech32, 19 | bech32m, 20 | bytes, 21 | createBase58check, 22 | hex, 23 | str, 24 | utils, 25 | } from '../lib/esm/index.js'; 26 | import { json, RANDOM } from './utils.js'; 27 | 28 | const base58check = createBase58check(sha256); 29 | const vectors = json('./vectors/base_vectors.json').v; 30 | 31 | const CODERS = { 32 | base32, 33 | base32hex, 34 | base32crockford, 35 | base64, 36 | base64url, 37 | base58, 38 | base58xmr, 39 | base58check, 40 | base58xrp, 41 | }; 42 | 43 | const NODE_CODERS = { 44 | hex: { 45 | encode: (buf) => Buffer.from(buf).toString('hex'), 46 | decode: (str) => Buffer.from(str, 'hex'), 47 | }, 48 | base64: { 49 | encode: (buf) => Buffer.from(buf).toString('base64'), 50 | decode: (str) => Buffer.from(str, 'base64'), 51 | }, 52 | }; 53 | 54 | for (const c in NODE_CODERS) { 55 | const node = NODE_CODERS[c]; 56 | should(`${c} against node`, () => { 57 | for (let i = 0; i < 1024; i++) { 58 | const buf = RANDOM.slice(0, i); 59 | 60 | const nodeStr = node.encode(buf); 61 | eql(nodeStr, str(c, buf), '111'); 62 | 63 | eql(hex.encode(bytes(c, nodeStr)), hex.encode(bytes(c, nodeStr)), '222'); 64 | } 65 | }); 66 | } 67 | 68 | should('14335 vectors, base32/64 58/hex/url/xmr, bech32/m', () => { 69 | for (let i = 0; i < vectors.length; i++) { 70 | const v = vectors[i]; 71 | const data = Uint8Array.from(Buffer.from(v.data, 'hex')); 72 | const coder = { 73 | base32, 74 | base32hex, 75 | base64, 76 | base64url, 77 | base58, 78 | base58xmr, 79 | bech32: { 80 | encode: (data) => bech32.encode('bech32', bech32.toWords(data), 9000), 81 | decode: (str) => bech32.fromWords(bech32.decode(str, 9000).words), 82 | }, 83 | bech32m: { 84 | encode: (data) => bech32m.encode('bech32m', bech32m.toWords(data), 9000), 85 | decode: (str) => bech32m.fromWords(bech32m.decode(str, 9000).words), 86 | }, 87 | }; 88 | eql(coder[v.fn_name].encode(data), v.exp, 'encode ' + i); 89 | eql(coder[v.fn_name].decode(v.exp), data, 'decode ' + i); 90 | } 91 | }); 92 | 93 | const TEST_BYTES = new TextEncoder().encode('@scure/base encoding / decoding'); 94 | 95 | should('nopad variants: base32', () => { 96 | eql(base32nopad.encode(TEST_BYTES), 'IBZWG5LSMUXWEYLTMUQGK3TDN5SGS3THEAXSAZDFMNXWI2LOM4'); 97 | eql(base32nopad.decode('IBZWG5LSMUXWEYLTMUQGK3TDN5SGS3THEAXSAZDFMNXWI2LOM4'), TEST_BYTES); 98 | eql(base32hexnopad.encode(TEST_BYTES), '81PM6TBICKNM4OBJCKG6ARJ3DTI6IRJ740NI0P35CDNM8QBECS'); 99 | eql(base32hexnopad.decode('81PM6TBICKNM4OBJCKG6ARJ3DTI6IRJ740NI0P35CDNM8QBECS'), TEST_BYTES); 100 | }); 101 | 102 | should('nopad variants: base64', () => { 103 | eql(base64nopad.encode(TEST_BYTES), 'QHNjdXJlL2Jhc2UgZW5jb2RpbmcgLyBkZWNvZGluZw'); 104 | eql(base64nopad.decode('QHNjdXJlL2Jhc2UgZW5jb2RpbmcgLyBkZWNvZGluZw'), TEST_BYTES); 105 | eql(base64urlnopad.encode(TEST_BYTES), 'QHNjdXJlL2Jhc2UgZW5jb2RpbmcgLyBkZWNvZGluZw'); 106 | eql(base64urlnopad.decode('QHNjdXJlL2Jhc2UgZW5jb2RpbmcgLyBkZWNvZGluZw'), TEST_BYTES); 107 | }); 108 | 109 | should('native base64 should ban spaces', () => { 110 | throws(() => { 111 | base64.decode('sxJ+knIJ1hI2snFHWiQEJb qEvknAX3vUieb0K7KmcHI='); 112 | }); 113 | }); 114 | 115 | should('utils: radix2', () => { 116 | const t = (bits) => { 117 | const coder = utils.radix2(bits); 118 | const val = new Uint8Array(1024).fill(0xff); 119 | const valPattern = Uint8Array.from({ length: 1024 }, (i, j) => j); 120 | eql(coder.decode(coder.encode(val)).slice(0, 1024), val, `radix2(${bits}, 0xff)`); 121 | eql( 122 | coder.decode(coder.encode(valPattern)).slice(0, 1024), 123 | valPattern, 124 | `radix2(${bits}, pattern)` 125 | ); 126 | }; 127 | throws(() => t(0)); 128 | for (let i = 1; i < 27; i++) t(i); 129 | throws(() => t(27)); // 34 bits 130 | t(28); 131 | throws(() => t(29)); // 36 bits 132 | throws(() => t(30)); // 36 bits 133 | throws(() => t(31)); // 38 bits 134 | t(32); // ok 135 | // true is not a number 136 | throws(() => utils.radix2(4).decode([1, true, 1, 1])); 137 | }); 138 | 139 | should('utils: radix', () => { 140 | const t = (base) => { 141 | const coder = utils.radix(base); 142 | const val = new Uint8Array(128).fill(0xff); 143 | const valPattern = Uint8Array.from({ length: 128 }, (i, j) => j); 144 | eql(coder.decode(coder.encode(val)).slice(0, 128), val, `radix(${base}, 0xff)`); 145 | eql( 146 | coder.decode(coder.encode(valPattern)).slice(0, 128), 147 | valPattern, 148 | `radix(${base}, pattern)` 149 | ); 150 | }; 151 | throws(() => t(1)); 152 | for (let i = 1; i < 46; i++) t(2 ** i); 153 | for (let i = 2; i < 46; i++) t(2 ** i - 1); 154 | for (let i = 1; i < 46; i++) t(2 ** i + 1); 155 | // carry overflows here 156 | t(35195299949887); 157 | throws(() => t(35195299949887 + 1)); 158 | throws(() => t(2 ** i)); 159 | // true is not a number 160 | throws(() => utils.radix(2 ** 4).decode([1, true, 1, 1])); 161 | }); 162 | 163 | should('utils: alphabet', () => { 164 | const a = utils.alphabet('12345'); 165 | const ab = utils.alphabet(['11', '2', '3', '4', '5']); 166 | eql(a.encode([1]), ['2']); 167 | eql(ab.encode([0]), ['11']); 168 | eql(a.encode([2]), ab.encode([2])); 169 | throws(() => a.encode([1, 2, true, 3])); 170 | throws(() => a.decode(['1', 2, true])); 171 | throws(() => a.decode(['1', 2])); 172 | throws(() => a.decode(['toString'])); 173 | }); 174 | 175 | should('utils: join', () => { 176 | throws(() => utils.join('1').encode(['1', 1, true])); 177 | }); 178 | 179 | should('utils: padding', () => { 180 | const coder = utils.padding(4, '='); 181 | throws(() => coder.encode(['1', 1, true])); 182 | throws(() => coder.decode(['1', 1, true, '='])); 183 | }); 184 | 185 | export { CODERS }; 186 | should.runWhen(import.meta.url); 187 | -------------------------------------------------------------------------------- /test/bech32.test.js: -------------------------------------------------------------------------------- 1 | import { should } from 'micro-should'; 2 | import { deepStrictEqual as eql, throws } from 'node:assert'; 3 | import { Buffer } from 'node:buffer'; 4 | import { bech32, bech32m } from '../lib/esm/index.js'; 5 | 6 | const BECH32_VALID = [ 7 | { string: 'A12UEL5L', prefix: 'A', words: [] }, 8 | { 9 | string: 10 | 'an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs', 11 | prefix: 'an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio', 12 | words: [], 13 | }, 14 | { 15 | string: 'abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw', 16 | prefix: 'abcdef', 17 | words: [ 18 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 19 | 26, 27, 28, 29, 30, 31, 20 | ], 21 | }, 22 | { 23 | string: 24 | '11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j', 25 | prefix: '1', 26 | words: [ 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | ], 31 | }, 32 | { 33 | string: 'split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w', 34 | prefix: 'split', 35 | words: [ 36 | 24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 37 | 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13, 38 | ], 39 | }, 40 | { 41 | string: 42 | '11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq978ear', 43 | prefix: '1', 44 | words: [ 45 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 51 | ], 52 | limit: 300, 53 | }, 54 | ]; 55 | 56 | const BECH32_INVALID_DECODE = [ 57 | 'A12Uel5l', 58 | ' 1nwldj5', 59 | 'abc1rzg', 60 | 'an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx', 61 | 'x1b4n0q5v', 62 | '1pzry9x0s0muk', 63 | 'pzry9x0s0muk', 64 | 'abc1rzgt4', 65 | 's1vcsyn', 66 | '11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j', 67 | 'li1dgmt3', 68 | Buffer.from('6465316c67377774ff', 'hex').toString('binary'), 69 | ]; 70 | 71 | const BECH32_INVALID_ENCODE = [ 72 | { 73 | prefix: '', 74 | words: [], 75 | }, 76 | { 77 | prefix: 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobar', 78 | words: [], 79 | }, 80 | { 81 | prefix: 'abc', 82 | words: [128], 83 | }, 84 | { 85 | prefix: 86 | 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobarfoobar', 87 | words: [128], 88 | }, 89 | { 90 | prefix: 'foobar', 91 | words: [ 92 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 93 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 94 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 95 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 96 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 97 | ], 98 | }, 99 | { 100 | prefix: 101 | 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobarfoobarfoobarfoobar', 102 | words: [128], 103 | limit: 104, 104 | }, 105 | ]; 106 | 107 | const BECH32M_VALID = [ 108 | { 109 | string: 'A1LQFN3A', 110 | prefix: 'A', 111 | words: [], 112 | }, 113 | { 114 | string: 'a1lqfn3a', 115 | prefix: 'a', 116 | words: [], 117 | }, 118 | { 119 | string: 120 | 'an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6', 121 | prefix: 'an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber1', 122 | words: [], 123 | }, 124 | { 125 | string: 'abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx', 126 | prefix: 'abcdef', 127 | words: [ 128 | 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 129 | 7, 6, 5, 4, 3, 2, 1, 0, 130 | ], 131 | }, 132 | { 133 | string: 134 | '11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8', 135 | prefix: '1', 136 | words: [ 137 | 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 138 | 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 139 | 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 140 | 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 141 | ], 142 | }, 143 | { 144 | string: 'split1checkupstagehandshakeupstreamerranterredcaperredlc445v', 145 | prefix: 'split', 146 | words: [ 147 | 24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 148 | 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13, 149 | ], 150 | }, 151 | { 152 | string: '?1v759aa', 153 | prefix: '?', 154 | words: [], 155 | }, 156 | { 157 | string: 158 | '11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqszh4cp', 159 | prefix: '1', 160 | words: [ 161 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 162 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 165 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 167 | ], 168 | limit: 300, 169 | }, 170 | ]; 171 | 172 | const BECH32M_INVALID_DECODE = [ 173 | 'A1LQfN3A', 174 | ' 1xj0phk', 175 | 'abc1rzg', 176 | 'an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4', 177 | 'qyrz8wqd2c9m', 178 | '1qyrz8wqd2c9m', 179 | 'y1b0jsk6g', 180 | 'lt1igcx5c0', 181 | 'in1muywd', 182 | 'mm1crxm3i', 183 | 'au1s5cgom', 184 | 'M1VUXWEZ', 185 | '16plkw9', 186 | '1p2gdwpf', 187 | '11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j', 188 | 'in1muywd', 189 | Buffer.from('6465316c67377774ff', 'hex').toString('binary'), 190 | ]; 191 | 192 | const BECH32M_INVALID_ENCODE = [ 193 | { 194 | prefix: '', 195 | words: [], 196 | }, 197 | { 198 | prefix: 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobar', 199 | words: [], 200 | }, 201 | { 202 | prefix: 'abc', 203 | words: [128], 204 | }, 205 | { 206 | prefix: 207 | 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobarfoobar', 208 | words: [128], 209 | }, 210 | { 211 | prefix: 'foobar', 212 | words: [ 213 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 214 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 215 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 216 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 217 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 218 | ], 219 | }, 220 | { 221 | prefix: 222 | 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobarfoobarfoobarfoobar', 223 | words: [128], 224 | limit: 104, 225 | }, 226 | ]; 227 | 228 | const INVALID_WORDS = [ 229 | [14, 20, 15, 7, 13, 26, 0, 25, 18, 6, 11, 13, 8, 21, 4, 20, 3, 17, 2, 29, 3, 0], 230 | [ 231 | 3, 1, 17, 17, 8, 15, 0, 20, 24, 20, 11, 6, 16, 1, 5, 29, 3, 4, 16, 3, 6, 21, 22, 26, 2, 13, 22, 232 | 9, 16, 21, 19, 24, 25, 21, 6, 18, 15, 8, 13, 24, 24, 24, 25, 9, 12, 1, 4, 16, 6, 9, 17, 1, 233 | ], 234 | ]; 235 | 236 | const VALID_WORDS = [ 237 | { 238 | hex: '00443214c74254b635cf84653a56d7c675be77df', 239 | words: [ 240 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 241 | 26, 27, 28, 29, 30, 31, 242 | ], 243 | }, 244 | { 245 | hex: '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 246 | words: [ 247 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 249 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250 | ], 251 | }, 252 | { 253 | hex: 'c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d', 254 | words: [ 255 | 24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 256 | 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13, 257 | ], 258 | }, 259 | { 260 | hex: 'ffbbcdeb38bdab49ca307b9ac5a928398a418820', 261 | words: [ 262 | 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 263 | 7, 6, 5, 4, 3, 2, 1, 0, 264 | ], 265 | }, 266 | { 267 | hex: '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 268 | words: [ 269 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 270 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 273 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 274 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 275 | ], 276 | }, 277 | ]; 278 | 279 | for (let v of BECH32M_VALID) { 280 | should(`encode ${v.prefix} ${v.words}`, () => { 281 | eql(bech32m.encode(v.prefix, v.words, v.limit), v.string.toLowerCase()); 282 | }); 283 | should(`encode/decode ${v.prefix} ${v.words}`, () => { 284 | const expected = { prefix: v.prefix.toLowerCase(), words: v.words }; 285 | eql(bech32m.decode(bech32m.encode(v.prefix, v.words, v.limit), v.limit), expected); 286 | }); 287 | should(`decode ${v.string}`, () => { 288 | const expected = { prefix: v.prefix.toLowerCase(), words: v.words }; 289 | eql(bech32m.decodeUnsafe(v.string, v.limit), expected); 290 | eql(bech32m.decode(v.string, v.limit), expected); 291 | }); 292 | should(`throw on ${v.string} with 1 bit flipped`, () => { 293 | const buffer = Buffer.from(v.string, 'utf8'); 294 | buffer[v.string.lastIndexOf('1') + 1] ^= 0x1; // flip a bit, after the prefix 295 | const str = buffer.toString('utf8'); 296 | eql(bech32m.decodeUnsafe(str, v.limit), undefined); 297 | throws(() => bech32m.decode(str, v.limit)); 298 | }); 299 | should(`throw on bech32m vector with bech32 ${v.string} `, () => { 300 | eql(bech32.decodeUnsafe(v.string, v.limit), undefined); 301 | throws(() => bech32.decode(v.string, v.limit)); 302 | }); 303 | } 304 | 305 | for (let v of BECH32_VALID) { 306 | should(`encode ${v.prefix} ${v.words}`, () => { 307 | eql(bech32.encode(v.prefix, v.words, v.limit), v.string.toLowerCase()); 308 | }); 309 | should(`encode/decode ${v.prefix} ${v.words}`, () => { 310 | const expected = { prefix: v.prefix.toLowerCase(), words: v.words }; 311 | eql(bech32.decode(bech32.encode(v.prefix, v.words, v.limit), v.limit), expected); 312 | }); 313 | should(`decode ${v.string}`, () => { 314 | const expected = { prefix: v.prefix.toLowerCase(), words: v.words }; 315 | eql(bech32.decodeUnsafe(v.string, v.limit), expected); 316 | eql(bech32.decode(v.string, v.limit), expected); 317 | }); 318 | should(`throw on ${v.string} with 1 bit flipped`, () => { 319 | const buffer = Buffer.from(v.string, 'utf8'); 320 | buffer[v.string.lastIndexOf('1') + 1] ^= 0x1; // flip a bit, after the prefix 321 | const str = buffer.toString('utf8'); 322 | eql(bech32.decodeUnsafe(str, v.limit), undefined); 323 | throws(() => bech32.decode(str, v.limit)); 324 | }); 325 | should(`throw on bech32 vector with bech32m ${v.string} `, () => { 326 | eql(bech32m.decodeUnsafe(v.string, v.limit), undefined); 327 | throws(() => bech32m.decode(v.string, v.limit)); 328 | }); 329 | } 330 | 331 | for (const str of BECH32_INVALID_DECODE) { 332 | should(`throw on decode ${str}`, () => { 333 | eql(bech32.decodeUnsafe(str), undefined); 334 | throws(() => bech32.decode(str)); 335 | }); 336 | } 337 | 338 | for (let v of BECH32_INVALID_ENCODE) { 339 | should(`throw on encode`, () => { 340 | throws(() => bech32.encode(v.prefix, v.words, v.limit)); 341 | }); 342 | } 343 | 344 | for (const str of BECH32M_INVALID_DECODE) { 345 | should(`throw on decode ${str} (bech32m)`, () => { 346 | eql(bech32m.decodeUnsafe(str), undefined); 347 | throws(() => bech32m.decode(str)); 348 | }); 349 | } 350 | 351 | for (let v of BECH32M_INVALID_ENCODE) { 352 | should(`throw on encode`, () => { 353 | throws(() => bech32m.encode(v.prefix, v.words, v.limit)); 354 | }); 355 | } 356 | 357 | for (let v of VALID_WORDS) { 358 | should(`fromWords/toWords ${v.hex}`, () => { 359 | const words = bech32.toWords(Buffer.from(v.hex, 'hex')); 360 | eql(Array.from(words), Array.from(v.words)); 361 | const bytes = Buffer.from(bech32.fromWords(v.words)); 362 | eql(bytes.toString('hex'), v.hex); 363 | const bytes2 = Buffer.from(bech32.fromWordsUnsafe(v.words)); 364 | eql(bytes2.toString('hex'), v.hex); 365 | }); 366 | } 367 | 368 | for (let v of INVALID_WORDS) { 369 | should(`throw om fromWords`, () => { 370 | eql(bech32.fromWordsUnsafe(v), undefined); 371 | throws(() => bech32.fromWords(v)); 372 | }); 373 | } 374 | 375 | should('toWords/toWordsUnsafe accept Uint8Array', () => { 376 | const bytes = new Uint8Array([0x00, 0x11, 0x22, 0x33, 0xff]); 377 | const words = bech32.toWords(bytes); 378 | eql(words, [0, 0, 8, 18, 4, 12, 31, 31]); 379 | }); 380 | 381 | should('encode accepts Uint8Array', () => { 382 | const bytes = new Uint8Array([0, 0, 8, 18, 4, 12, 31, 31]); 383 | eql(bech32.encode('test', bytes), 'test1qqgjyvlld2nz37'); 384 | }); 385 | 386 | should.runWhen(import.meta.url); 387 | -------------------------------------------------------------------------------- /test/benchmark/index.js: -------------------------------------------------------------------------------- 1 | const { mark: bench } = require('micro-bmark'); 2 | const { base64, base58, hex, base64url } = require('../..'); 3 | const stableBase64 = require('@stablelib/base64'); 4 | const microBase58 = require('micro-base58'); 5 | const stableHex = require('@stablelib/hex'); 6 | const nodeBase58 = require('@faustbrian/node-base58'); 7 | const bs58 = require('bs58'); 8 | 9 | const CODERS = { 10 | Hex: { 11 | encode: { 12 | node: (buf) => Buffer.from(buf).toString('hex'), 13 | stable: (buf) => stableHex.encode(buf), 14 | scure: (buf) => hex.encode(buf), 15 | }, 16 | decode: { 17 | node: (str) => Buffer.from(str, 'hex'), 18 | stable: (str) => stableHex.decode(str), 19 | scure: (str) => hex.decode(str), 20 | }, 21 | }, 22 | Base64: { 23 | encode: { 24 | node: (buf) => Buffer.from(buf).toString('base64'), 25 | stable: (buf) => stableBase64.encode(buf), 26 | scure: (buf) => base64.encode(buf), 27 | scure_url: (buf) => base64url.encode(buf), 28 | }, 29 | decode: { 30 | node: (str) => Buffer.from(str, 'base64'), 31 | stable: (str) => stableBase64.decode(str), 32 | scure: (str) => base64.decode(str), 33 | scure_url: (str) => base64url.decode(str), 34 | }, 35 | }, 36 | Base58: { 37 | encode: { 38 | nodeBase58: (buf) => nodeBase58.encode(buf), 39 | bs58: (buf) => bs58.encode(buf), 40 | micro: (buf) => microBase58.encode(buf), 41 | scure: (buf) => base58.encode(buf), 42 | }, 43 | decode: { 44 | nodeBase58: (str) => nodeBase58.decode(str), 45 | bs58: (str) => bs58.decode(str), 46 | micro: (str) => microBase58.decode(str), 47 | scure: (str) => base58.decode(str), 48 | }, 49 | }, 50 | }; 51 | 52 | // buffer title, sample count, data 53 | const buffers = { 54 | // '32 B': [1000000, new Uint8Array(32).fill(1)], 55 | // '64 B': [250000, new Uint8Array(64).fill(1)], 56 | '1 KB': [50000, new Uint8Array(1024).fill(2)], 57 | // '8 KB': [5000, new Uint8Array(1024 * 8).fill(3)], 58 | // Slow, but 100 doesn't show difference, probably opt doesn't happen or something 59 | // '1 MB': [10, new Uint8Array(1024 * 1024).fill(4)], 60 | }; 61 | 62 | const main = () => 63 | (async () => { 64 | for (let [k, libs] of Object.entries(CODERS)) { 65 | console.log(`==== ${k} ====`); 66 | for (const [size, [samples, buf]] of Object.entries(buffers)) { 67 | for (const [lib, fn] of Object.entries(libs.encode)) 68 | await bench(`${k} (encode) ${size} ${lib}`, () => fn(buf)); 69 | console.log(); 70 | const str = libs.encode.scure(buf); 71 | for (const [lib, fn] of Object.entries(libs.decode)) 72 | await bench(`${k} (decode) ${size} ${lib}`, () => fn(str)); 73 | console.log(); 74 | } 75 | } 76 | })(); 77 | 78 | module.exports = { main }; 79 | if (require.main === module) main(); 80 | -------------------------------------------------------------------------------- /test/benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scure-base-benchmark", 3 | "version": "0.0.1", 4 | "description": "Benchmark suite for scure-base", 5 | "main": "./index.js", 6 | "scripts": { 7 | "bench": "node ./index.js" 8 | }, 9 | "devDependencies": { 10 | "micro-should": "0.4.0", 11 | "typescript": "5.5.2" 12 | }, 13 | "dependencies": { 14 | "@faustbrian/node-base58": "1.0.0", 15 | "@stablelib/base64": "1.0.1", 16 | "@stablelib/hex": "1.0.1", 17 | "base-x": "3.0.8", 18 | "bs58": "4.0.1", 19 | "micro-base58": "0.5.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/benchmark/quadratic.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert'); 2 | const { should } = require('micro-should'); 3 | const { RANDOM, stats } = require('../test/utils'); 4 | 5 | const microBase58 = require('micro-base58'); 6 | const nodeBase58 = require('@faustbrian/node-base58'); 7 | const bs58 = require('bs58'); 8 | 9 | const getTime = () => Number(process.hrtime.bigint()); 10 | 11 | // Median execution time for callback (reduce noise) 12 | async function bench(callback, iters = 10) { 13 | const timings = []; 14 | for (let i = 0; i < iters; i++) { 15 | const ts = getTime(); 16 | let val = callback(); 17 | if (val instanceof Promise) await val; 18 | timings.push(getTime() - ts); 19 | } 20 | return stats(timings).median; 21 | } 22 | // Handle flaky tests. If complexity test passed even 1 of 5 attempts, then its ok. 23 | // Only when all attempts failed, test is failed. 24 | const retry = 25 | (callback, retries = 5) => 26 | async () => { 27 | for (let i = 0; i < retries - 1; i++) { 28 | try { 29 | return await callback(); 30 | } catch (e) {} 31 | } 32 | // last attempt, throw exception if failed 33 | return await callback(); 34 | }; 35 | 36 | // O(N) 37 | function linear(buf) { 38 | for (let i = 0; i < buf.length; i++); 39 | } 40 | // O(128*1024*N) 41 | function linearConst(buf) { 42 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < 16 * 1024; j++); 43 | } 44 | // O(N*log2(N)) 45 | function log2(buf) { 46 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < Math.log2(buf.length); j++); 47 | } 48 | // O(N*log10(N)) 49 | function log10(buf) { 50 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < Math.log10(buf.length); j++); 51 | } 52 | // O(N^2) 53 | function quadratic(buf) { 54 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < buf.length; j++); 55 | } 56 | // Should be around 0.1, but its significantly depends on environment, GC, other processes that run in parallel. Which makes tests too flaky. 57 | const MARGIN = (() => { 58 | const timings = []; 59 | for (let i = 0; i < 5; i++) { 60 | let ts = getTime(); 61 | linearConst(1024); 62 | timings.push((getTime() - ts) / 1024); 63 | } 64 | const diff = Math.max(...stats(timings).difference.map((i) => Math.abs(i))); 65 | return Math.max(1, diff); 66 | })(); 67 | 68 | console.log(`Time margin: ${MARGIN}`); 69 | 70 | const SMALL_BUF = new Uint8Array(1024); 71 | // Check that there is linear relation between input size and running time of callback 72 | async function isLinear(callback, iters = 128) { 73 | // Warmup && trigger JIT 74 | for (let i = 0; i < 1024; i++) await callback(SMALL_BUF); 75 | // Measure difference between relative execution time (per byte) 76 | const timings = []; 77 | for (let i = 1; i < iters; i++) { 78 | const buf = RANDOM.subarray(0, 1024 * i); 79 | const time = await bench(() => callback(buf)); 80 | timings.push(time / buf.length); // time per byte 81 | } 82 | // Median of differences. Should be close to zero for linear functions (+/- some noise). 83 | const medianDifference = stats(stats(timings.map((i) => i)).difference).median; 84 | console.log({ medianDifference }); 85 | assert.deepStrictEqual( 86 | medianDifference < MARGIN, 87 | true, 88 | `medianDifference(${medianDifference}) should be less than ${MARGIN}` 89 | ); 90 | } 91 | 92 | // Verify that it correctly detects functions with quadratic complexity 93 | should( 94 | 'detect quadratic functions', 95 | retry(async () => { 96 | // 16 iters since quadratic is very slow 97 | console.log('Linear'); 98 | await isLinear((buf) => linear(buf), 16); 99 | console.log('Linear const'); 100 | await isLinear((buf) => linearConst(buf), 16); 101 | // Very close to linear, not much impact 102 | console.log('Log2'); 103 | await isLinear((buf) => log2(buf), 16); 104 | console.log('Log10'); 105 | await isLinear((buf) => log10(buf), 16); 106 | console.log('Quadratic'); 107 | await assert.rejects(() => isLinear((buf) => quadratic(buf), 16)); 108 | }) 109 | ); 110 | 111 | should( 112 | `DoS: bs58 is quadratic :(`, 113 | retry(async () => { 114 | await assert.rejects(() => isLinear((buf) => bs58.decode(bs58.encode(buf)), 16)); 115 | }) 116 | ); 117 | 118 | should( 119 | `DoS: microBase58 is quadratic :(`, 120 | retry(async () => { 121 | await assert.rejects(() => isLinear((buf) => microBase58.decode(microBase58.encode(buf)), 16)); 122 | }) 123 | ); 124 | 125 | should( 126 | `DoS: nodeBase58 is quadratic :(`, 127 | retry(async () => { 128 | await assert.rejects(() => isLinear((buf) => nodeBase58.decode(nodeBase58.encode(buf)), 16)); 129 | }) 130 | ); 131 | 132 | if (require.main === module) should.run(); 133 | -------------------------------------------------------------------------------- /test/bip173.test.js: -------------------------------------------------------------------------------- 1 | import { describe, should } from 'micro-should'; 2 | import { deepStrictEqual as eql, throws } from 'node:assert'; 3 | import { Buffer } from 'node:buffer'; 4 | import { bech32, bech32m } from '../lib/esm/index.js'; 5 | 6 | const VALID = [ 7 | ['BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', '751e76e8199196d454941c45d1b3a323f1433bd6'], 8 | [ 9 | 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7', 10 | '1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262', 11 | ], 12 | ['BC1SW50QA3JX3S', '751e'], 13 | ['bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj', '751e76e8199196d454941c45d1b3a323'], 14 | [ 15 | 'tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy', 16 | '000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 17 | ], 18 | ]; 19 | 20 | const INVALID = [ 21 | [ 22 | 'an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx', 23 | 'overall max length exceeded', 24 | ], 25 | ['pzry9x0s0muk', 'No separator character'], 26 | ['1pzry9x0s0muk', 'Empty HRP'], 27 | ['x1b4n0q5v', 'Invalid data character'], 28 | ['li1dgmt3', 'Too short checksum'], 29 | ['A1G7SGD8', 'checksum calculated with uppercase form of HRP'], 30 | ['10a06t8', 'empty HRP'], 31 | ['1qzzfhee', 'empty HRP'], 32 | ['bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', 'Invalid checksum'], 33 | ['BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2', 'Invalid witness version'], 34 | ['bc1rw5uspcuh', 'Invalid program length'], 35 | [ 36 | 'bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90', 37 | 'Invalid program length', 38 | ], 39 | ['bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du', 'zero padding of more than 4 bits'], 40 | [ 41 | 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv', 42 | 'Non-zero padding in 8-to-5 conversion', 43 | ], 44 | ['bc1gmk9yu', 'Empty data section'], 45 | ]; 46 | 47 | // GH-17 48 | function decodeBtc(address) { 49 | const decoded = bech32.decode(address); 50 | // NOTE: words in bitcoin addresses contain version as first element, 51 | // with actual witnes program words in rest 52 | // BIP-141: The value of the first push is called the "version byte". 53 | // The following byte vector pushed is called the "witness program". 54 | const [ver, ...dataW] = decoded.words; 55 | // MUST verify that the first decoded data value (the witness version) 56 | // is between 0 and 16, inclusive. 57 | if (ver < 0 || ver > 16) throw new Error('wrong version'); 58 | const program = bech32.fromWords(dataW); 59 | // BIP141 specifies If the version byte is 0, but the witness program 60 | // is neither 20 nor 32 bytes, the script must fail. 61 | if (ver === 0 && program.length !== 20 && program.length !== 32) 62 | throw new Error('wrong program length'); 63 | // followed by a data push between 2 and 40 bytes gets a new special meaning. 64 | if (program.length < 2 || program.length > 40) throw new Error('wrong program length'); 65 | return Buffer.from(program).toString('hex'); 66 | } 67 | 68 | describe('bip173', () => { 69 | describe('valid', () => { 70 | for (const [v, hex] of VALID) { 71 | should(`valid ${v}`, () => eql(decodeBtc(v), hex)); 72 | } 73 | }); 74 | for (const [v, description] of INVALID) { 75 | should(`invalid: ${v} (${description})`, () => throws(() => decodeBtc(v))); 76 | } 77 | }); 78 | 79 | // Official vectors include checksum by some reason, and we don't export 80 | // checksum from parser. We cross-tested against official demo at 81 | // https://bitcoin.sipa.be/bech32/demo/demo.html 82 | // prettier-ignore 83 | const VALID_BIP350 = [ 84 | // [address, realHex, hexWithChecksumFromBip350] 85 | [ 86 | 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', 87 | '751e76e8199196d454941c45d1b3a323f1433bd6', 88 | '0014751e76e8199196d454941c45d1b3a323f1433bd6', 89 | ], 90 | [ 91 | 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7', 92 | '1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262', 93 | '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262', 94 | ], 95 | [ 96 | 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y', 97 | '751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6', 98 | '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6', 99 | ], 100 | [ 101 | 'BC1SW50QGDZ25J', 102 | '751e', 103 | '6002751e' 104 | ], 105 | ['bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs', 106 | '751e76e8199196d454941c45d1b3a323', 107 | '5210751e76e8199196d454941c45d1b3a323' 108 | ], 109 | [ 110 | 'tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy', 111 | '000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 112 | '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 113 | ], 114 | [ 115 | 'tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c', 116 | '000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 117 | '5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 118 | ], 119 | [ 120 | 'bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0', 121 | '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 122 | '512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 123 | ], 124 | ]; 125 | 126 | const INVALID_BIP350 = [ 127 | ['tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut', 'Invalid human-readable part'], 128 | [ 129 | 'bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd', 130 | 'Invalid checksum (Bech32 instead of Bech32m)', 131 | ], 132 | [ 133 | 'tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf', 134 | 'Invalid checksum (Bech32 instead of Bech32m)', 135 | ], 136 | [ 137 | 'BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL', 138 | 'Invalid checksum (Bech32 instead of Bech32m)', 139 | ], 140 | ['bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh', 'Invalid checksum (Bech32m instead of Bech32)'], 141 | [ 142 | 'tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47', 143 | 'Invalid checksum (Bech32m instead of Bech32)', 144 | ], 145 | [ 146 | 'bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4', 147 | 'Invalid character in checksum', 148 | ], 149 | ['BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R', 'Invalid witness version'], 150 | ['bc1pw5dgrnzv', 'Invalid program length (1 byte)'], 151 | [ 152 | 'bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav', 153 | 'Invalid program length (41 bytes)', 154 | ], 155 | [ 156 | 'BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P', 157 | 'Invalid program length for witness version 0 (per BIP141)', 158 | ], 159 | ['tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq', 'Mixed case'], 160 | [ 161 | 'bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf', 162 | 'zero padding of more than 4 bits', 163 | ], 164 | [ 165 | 'tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j', 166 | 'Non-zero padding in 8-to-5 conversion', 167 | ], 168 | ['bc1gmk9yu', 'Empty data section'], 169 | ]; 170 | 171 | function decodeBtc350(address) { 172 | let decoded, 173 | err, 174 | isb32m = false; 175 | try { 176 | decoded = bech32.decode(address); 177 | } catch (err) { 178 | err = err; 179 | } 180 | try { 181 | decoded = bech32m.decode(address); 182 | isb32m = true; 183 | } catch (err) { 184 | err = err; 185 | } 186 | if (!decoded) throw err; 187 | // The human-readable part "bc"[7] for mainnet, and "tb"[8] for testnet. 188 | if (!['bc', 'tb'].includes(decoded.prefix)) throw new Error('Invalid prefix'); 189 | const [ver, ...dataW] = decoded.words; 190 | if (isb32m && ver === 0) throw new Error('Witness program version 0 should use bech32'); 191 | if (!isb32m && ver >= 1) throw new Error('Witness program with version >=1 should use bech32m'); 192 | // MUST verify that the first decoded data value (the witness version) 193 | // is between 0 and 16, inclusive. 194 | if (ver < 0 || ver > 16) throw new Error('wrong version'); 195 | // NOTE: words in bitcoin addresses contain version as first element, 196 | // with actual witnes program words in rest 197 | // BIP-141: The value of the first push is called the "version byte". 198 | // The following byte vector pushed is called the "witness program". 199 | const program = (isb32m ? bech32m : bech32).fromWords(dataW); 200 | // BIP141 specifies If the version byte is 0, but the witness program 201 | // is neither 20 nor 32 bytes, the script must fail. 202 | if (ver === 0 && program.length !== 20 && program.length !== 32) 203 | throw new Error('wrong program length'); 204 | // followed by a data push between 2 and 40 bytes gets a new special meaning. 205 | if (program.length < 2 || program.length > 40) throw new Error('wrong program length'); 206 | return Buffer.from(program).toString('hex'); 207 | } 208 | 209 | describe('bip350', () => { 210 | describe('valid', () => { 211 | for (const [v, hex] of VALID_BIP350) { 212 | should(`valid ${v}`, () => eql(decodeBtc350(v), hex)); 213 | } 214 | }); 215 | for (const [v, description] of INVALID_BIP350) { 216 | should(`invalid: ${v} (${description})`, () => throws(() => decodeBtc350(v))); 217 | } 218 | }); 219 | 220 | // BOLT11 (https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) 221 | // NOTE: this is just example how parsing can be done, 222 | // it is not a production-ready library for parsing lightning invoices. 223 | // This protocol uses 5-bit words as is for significant part of parser and 224 | // convert to bytes only inside tagged fields. Can be very nice small parser 225 | // using micro-packed, but unfortunately micro-packed only works with bytes 226 | // for now, and this protocol is based on 5-bit words. 227 | should('lightning invoices (GH-18)', () => { 228 | /* 229 | URL: https://lightningdecoder.com/lnbc1u1pjvy84epp5zrapr3w7tqelvjzwm0rwsac2ga79m982uruducydr2u6zwlhpasqhp5fe47lwjexge0lff7ru2g6757g35qajscy39hsz4dvqe97gnt3d3scqzzsxqyz5vqsp5ptv9dz544r5pxd3gkulqelakrtmx4nf47xw4mmm8a0u8j2up7mqs9qyyssqumxjespzkuwzdppw3hzkawgdedjyu2e0wnsk3t3y8g7mkpz49nn9rlrzsj07tz3hjnld80j749069puz9uanhr55p9ngw46cy2w295qpktsz9y 230 | Recovery Flag: 1 231 | Transaction Signature: 232 | e6cd2cc022b71c26842e8dc56eb90dcb644e2b2f74e168ae243a3dbb04552ce651fc62849fe58a3794fed3be5ea95fa287822f3b3b8e940966875758229ca2d0 233 | Timestamp: 1690443449 234 | Payment Hash 235 | 10fa11c5de5833f6484edbc6e8770a477c5d94eae0f8de608d1ab9a13bf70f60 236 | Commit Hash 237 | 4e6befba593232ffa53e1f148d7a9e44680eca18244b780aad60325f226b8b63 238 | Minimum Final CLTV Expiry 239 | 80 240 | Expire Time 241 | 86400 242 | */ 243 | const test_enc = 244 | 'lnbc1u1pjvy84epp5zrapr3w7tqelvjzwm0rwsac2ga79m982uruducydr2u6zwlhpasqhp5fe47lwjexge0lff7ru2g6757g35qajscy39hsz4dvqe97gnt3d3scqzzsxqyz5vqsp5ptv9dz544r5pxd3gkulqelakrtmx4nf47xw4mmm8a0u8j2up7mqs9qyyssqumxjespzkuwzdppw3hzkawgdedjyu2e0wnsk3t3y8g7mkpz49nn9rlrzsj07tz3hjnld80j749069puz9uanhr55p9ngw46cy2w295qpktsz9y'; 245 | const decoded = bech32.decode(test_enc, false); // Disable length limit 246 | // lnbc -- mainnet 247 | // amount: 1 248 | // quantity: u (micro): multiply by 0.000001 249 | eql(decoded.prefix, 'lnbc1u'); 250 | // signature: Bitcoin-style signature of above (520 bits), 104 words 251 | const sigTmp = decoded.words.slice(-104); 252 | const recoveryFlag = decoded.words[decoded.words.length - 1]; 253 | eql(recoveryFlag, 1, 'Recovery Flag'); 254 | const signature = bech32.fromWords(sigTmp.slice(0, -1)); // Strip recovery flag 255 | eql(signature.length, 64); 256 | eql( 257 | Buffer.from(signature).toString('hex'), 258 | 'e6cd2cc022b71c26842e8dc56eb90dcb644e2b2f74e168ae243a3dbb04552ce651fc62849fe58a3794fed3be5ea95fa287822f3b3b8e940966875758229ca2d0', 259 | 'Transaction Signature' 260 | ); 261 | // Convert array of 5 bit words to big-endian number 262 | const toInt = (words) => { 263 | // 2**52 < Number.MAX_SAFE_INTEGER = true 264 | // 10x5bits maximum (would be 2**50). 2**55 is not safe. 265 | if (words.length <= 0 || words.length > 10) throw new Error('Words array should be (0, 10]'); 266 | let res = 0; 267 | for (let i = 0; i < words.length; i++) { 268 | res += words[i]; 269 | // Cannot use shifts here, since number can be bigger than 32 bits 270 | if (i !== words.length - 1) res *= 2 ** 5; 271 | } 272 | return res; 273 | }; 274 | // timestamp: seconds-since-1970 (35 bits, big-endian), 7 words 275 | const ts = toInt(decoded.words.slice(0, 7)); 276 | eql(ts, 1690443449, 'Timestamp'); 277 | // zero or more tagged parts 278 | let words = decoded.words.slice(7, -104); // without signature && timestamp 279 | const tags = []; 280 | while (words.length) { 281 | const tag = words[0]; 282 | words = words.slice(1); // skip tag 283 | const len = toInt(words.slice(0, 2)); 284 | words = words.slice(2); // skip tagLength 285 | const data = words.slice(0, len); 286 | tags.push({ tag, data }); 287 | words = words.slice(len); // skip data 288 | } 289 | // p (1): data_length 52. 256-bit SHA256 payment_hash. Preimage of this 290 | // provides proof of payment. 291 | eql(tags[0].tag, 1); // tagged type: preimage 292 | const preimage = bech32.fromWords(tags[0].data); 293 | eql( 294 | Buffer.from(preimage).toString('hex'), 295 | '10fa11c5de5833f6484edbc6e8770a477c5d94eae0f8de608d1ab9a13bf70f60', 296 | 'Payment Hash' 297 | ); 298 | // h (23): data_length 52. 256-bit description of purpose of payment (SHA256). 299 | // This is used to commit to an associated description that is over 639 bytes, 300 | // but the transport mechanism for the description in that case is transport 301 | // specific and not defined here. 302 | eql(tags[1].tag, 23); 303 | const h = bech32.fromWords(tags[1].data); 304 | eql( 305 | Buffer.from(h).toString('hex'), 306 | '4e6befba593232ffa53e1f148d7a9e44680eca18244b780aad60325f226b8b63', 307 | 'Commit Hash' 308 | ); 309 | // c (24): data_length variable. min_final_cltv_expiry_delta to use for the 310 | // last HTLC in the route. Default is 18 if not specified. 311 | eql(tags[2].tag, 24); 312 | eql(toInt(tags[2].data), 80); 313 | // x (6): data_length variable. expiry time in seconds (big-endian). Default 314 | // is 3600 (1 hour) if not specified. 315 | eql(tags[3].tag, 6); 316 | eql(toInt(tags[3].data), 86400); 317 | // s (16): data_length 52. This 256-bit secret 318 | // prevents forwarding nodes from probing the payment recipient. 319 | eql(tags[4].tag, 16); 320 | // NOTE: Not shown in web decoder 321 | eql( 322 | Buffer.from(bech32.fromWords(tags[4].data)).toString('hex'), 323 | '0ad8568a95a8e8133628b73e0cffb61af66acd35f19d5def67ebf8792b81f6c1', 324 | 'Secret' 325 | ); 326 | // 9 (5): data_length variable. One or more 5-bit values containing features 327 | // supported or required for receiving this payment. See Feature Bits. 328 | eql(tags[5].tag, 5); 329 | // NOTE: not shown in web decoder 330 | eql(Buffer.from(bech32.fromWords(tags[5].data)).toString('hex'), '2420', 'Feature bits'); 331 | eql(tags.length, 6); 332 | }); 333 | 334 | should.runWhen(import.meta.url); 335 | -------------------------------------------------------------------------------- /test/build/input.js: -------------------------------------------------------------------------------- 1 | export { base16, base32, base64, base58 } from '@scure/base'; 2 | export { 3 | base58xmr, 4 | base58xrp, 5 | base32hex, 6 | base32crockford, 7 | base64nopad, 8 | base64url, 9 | base64urlnopad, 10 | } from '@scure/base'; 11 | export { createBase58check, bech32, bech32m, hex } from '@scure/base'; 12 | -------------------------------------------------------------------------------- /test/build/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "build", 9 | "version": "1.0.0", 10 | "devDependencies": { 11 | "@scure/base": "file:../..", 12 | "esbuild": "0.25.0" 13 | } 14 | }, 15 | "..": { 16 | "name": "benchmark", 17 | "version": "0.1.0", 18 | "extraneous": true 19 | }, 20 | "../..": { 21 | "name": "@scure/base", 22 | "version": "1.2.6", 23 | "dev": true, 24 | "license": "MIT", 25 | "devDependencies": { 26 | "@noble/hashes": "1.8.0", 27 | "@paulmillr/jsbt": "0.3.3", 28 | "@types/node": "22.15.23", 29 | "fast-check": "4.1.1", 30 | "micro-bmark": "0.4.2", 31 | "micro-should": "0.5.3", 32 | "prettier": "3.5.3", 33 | "typescript": "5.8.3" 34 | }, 35 | "funding": { 36 | "url": "https://paulmillr.com/funding/" 37 | } 38 | }, 39 | "node_modules/@esbuild/aix-ppc64": { 40 | "version": "0.25.0", 41 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", 42 | "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", 43 | "cpu": [ 44 | "ppc64" 45 | ], 46 | "dev": true, 47 | "license": "MIT", 48 | "optional": true, 49 | "os": [ 50 | "aix" 51 | ], 52 | "engines": { 53 | "node": ">=18" 54 | } 55 | }, 56 | "node_modules/@esbuild/android-arm": { 57 | "version": "0.25.0", 58 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", 59 | "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", 60 | "cpu": [ 61 | "arm" 62 | ], 63 | "dev": true, 64 | "license": "MIT", 65 | "optional": true, 66 | "os": [ 67 | "android" 68 | ], 69 | "engines": { 70 | "node": ">=18" 71 | } 72 | }, 73 | "node_modules/@esbuild/android-arm64": { 74 | "version": "0.25.0", 75 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", 76 | "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", 77 | "cpu": [ 78 | "arm64" 79 | ], 80 | "dev": true, 81 | "license": "MIT", 82 | "optional": true, 83 | "os": [ 84 | "android" 85 | ], 86 | "engines": { 87 | "node": ">=18" 88 | } 89 | }, 90 | "node_modules/@esbuild/android-x64": { 91 | "version": "0.25.0", 92 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", 93 | "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", 94 | "cpu": [ 95 | "x64" 96 | ], 97 | "dev": true, 98 | "license": "MIT", 99 | "optional": true, 100 | "os": [ 101 | "android" 102 | ], 103 | "engines": { 104 | "node": ">=18" 105 | } 106 | }, 107 | "node_modules/@esbuild/darwin-arm64": { 108 | "version": "0.25.0", 109 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", 110 | "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "dev": true, 115 | "license": "MIT", 116 | "optional": true, 117 | "os": [ 118 | "darwin" 119 | ], 120 | "engines": { 121 | "node": ">=18" 122 | } 123 | }, 124 | "node_modules/@esbuild/darwin-x64": { 125 | "version": "0.25.0", 126 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", 127 | "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", 128 | "cpu": [ 129 | "x64" 130 | ], 131 | "dev": true, 132 | "license": "MIT", 133 | "optional": true, 134 | "os": [ 135 | "darwin" 136 | ], 137 | "engines": { 138 | "node": ">=18" 139 | } 140 | }, 141 | "node_modules/@esbuild/freebsd-arm64": { 142 | "version": "0.25.0", 143 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", 144 | "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", 145 | "cpu": [ 146 | "arm64" 147 | ], 148 | "dev": true, 149 | "license": "MIT", 150 | "optional": true, 151 | "os": [ 152 | "freebsd" 153 | ], 154 | "engines": { 155 | "node": ">=18" 156 | } 157 | }, 158 | "node_modules/@esbuild/freebsd-x64": { 159 | "version": "0.25.0", 160 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", 161 | "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", 162 | "cpu": [ 163 | "x64" 164 | ], 165 | "dev": true, 166 | "license": "MIT", 167 | "optional": true, 168 | "os": [ 169 | "freebsd" 170 | ], 171 | "engines": { 172 | "node": ">=18" 173 | } 174 | }, 175 | "node_modules/@esbuild/linux-arm": { 176 | "version": "0.25.0", 177 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", 178 | "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", 179 | "cpu": [ 180 | "arm" 181 | ], 182 | "dev": true, 183 | "license": "MIT", 184 | "optional": true, 185 | "os": [ 186 | "linux" 187 | ], 188 | "engines": { 189 | "node": ">=18" 190 | } 191 | }, 192 | "node_modules/@esbuild/linux-arm64": { 193 | "version": "0.25.0", 194 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", 195 | "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", 196 | "cpu": [ 197 | "arm64" 198 | ], 199 | "dev": true, 200 | "license": "MIT", 201 | "optional": true, 202 | "os": [ 203 | "linux" 204 | ], 205 | "engines": { 206 | "node": ">=18" 207 | } 208 | }, 209 | "node_modules/@esbuild/linux-ia32": { 210 | "version": "0.25.0", 211 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", 212 | "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", 213 | "cpu": [ 214 | "ia32" 215 | ], 216 | "dev": true, 217 | "license": "MIT", 218 | "optional": true, 219 | "os": [ 220 | "linux" 221 | ], 222 | "engines": { 223 | "node": ">=18" 224 | } 225 | }, 226 | "node_modules/@esbuild/linux-loong64": { 227 | "version": "0.25.0", 228 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", 229 | "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", 230 | "cpu": [ 231 | "loong64" 232 | ], 233 | "dev": true, 234 | "license": "MIT", 235 | "optional": true, 236 | "os": [ 237 | "linux" 238 | ], 239 | "engines": { 240 | "node": ">=18" 241 | } 242 | }, 243 | "node_modules/@esbuild/linux-mips64el": { 244 | "version": "0.25.0", 245 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", 246 | "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", 247 | "cpu": [ 248 | "mips64el" 249 | ], 250 | "dev": true, 251 | "license": "MIT", 252 | "optional": true, 253 | "os": [ 254 | "linux" 255 | ], 256 | "engines": { 257 | "node": ">=18" 258 | } 259 | }, 260 | "node_modules/@esbuild/linux-ppc64": { 261 | "version": "0.25.0", 262 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", 263 | "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", 264 | "cpu": [ 265 | "ppc64" 266 | ], 267 | "dev": true, 268 | "license": "MIT", 269 | "optional": true, 270 | "os": [ 271 | "linux" 272 | ], 273 | "engines": { 274 | "node": ">=18" 275 | } 276 | }, 277 | "node_modules/@esbuild/linux-riscv64": { 278 | "version": "0.25.0", 279 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", 280 | "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", 281 | "cpu": [ 282 | "riscv64" 283 | ], 284 | "dev": true, 285 | "license": "MIT", 286 | "optional": true, 287 | "os": [ 288 | "linux" 289 | ], 290 | "engines": { 291 | "node": ">=18" 292 | } 293 | }, 294 | "node_modules/@esbuild/linux-s390x": { 295 | "version": "0.25.0", 296 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", 297 | "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", 298 | "cpu": [ 299 | "s390x" 300 | ], 301 | "dev": true, 302 | "license": "MIT", 303 | "optional": true, 304 | "os": [ 305 | "linux" 306 | ], 307 | "engines": { 308 | "node": ">=18" 309 | } 310 | }, 311 | "node_modules/@esbuild/linux-x64": { 312 | "version": "0.25.0", 313 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", 314 | "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", 315 | "cpu": [ 316 | "x64" 317 | ], 318 | "dev": true, 319 | "license": "MIT", 320 | "optional": true, 321 | "os": [ 322 | "linux" 323 | ], 324 | "engines": { 325 | "node": ">=18" 326 | } 327 | }, 328 | "node_modules/@esbuild/netbsd-arm64": { 329 | "version": "0.25.0", 330 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", 331 | "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", 332 | "cpu": [ 333 | "arm64" 334 | ], 335 | "dev": true, 336 | "license": "MIT", 337 | "optional": true, 338 | "os": [ 339 | "netbsd" 340 | ], 341 | "engines": { 342 | "node": ">=18" 343 | } 344 | }, 345 | "node_modules/@esbuild/netbsd-x64": { 346 | "version": "0.25.0", 347 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", 348 | "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", 349 | "cpu": [ 350 | "x64" 351 | ], 352 | "dev": true, 353 | "license": "MIT", 354 | "optional": true, 355 | "os": [ 356 | "netbsd" 357 | ], 358 | "engines": { 359 | "node": ">=18" 360 | } 361 | }, 362 | "node_modules/@esbuild/openbsd-arm64": { 363 | "version": "0.25.0", 364 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", 365 | "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", 366 | "cpu": [ 367 | "arm64" 368 | ], 369 | "dev": true, 370 | "license": "MIT", 371 | "optional": true, 372 | "os": [ 373 | "openbsd" 374 | ], 375 | "engines": { 376 | "node": ">=18" 377 | } 378 | }, 379 | "node_modules/@esbuild/openbsd-x64": { 380 | "version": "0.25.0", 381 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", 382 | "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", 383 | "cpu": [ 384 | "x64" 385 | ], 386 | "dev": true, 387 | "license": "MIT", 388 | "optional": true, 389 | "os": [ 390 | "openbsd" 391 | ], 392 | "engines": { 393 | "node": ">=18" 394 | } 395 | }, 396 | "node_modules/@esbuild/sunos-x64": { 397 | "version": "0.25.0", 398 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", 399 | "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", 400 | "cpu": [ 401 | "x64" 402 | ], 403 | "dev": true, 404 | "license": "MIT", 405 | "optional": true, 406 | "os": [ 407 | "sunos" 408 | ], 409 | "engines": { 410 | "node": ">=18" 411 | } 412 | }, 413 | "node_modules/@esbuild/win32-arm64": { 414 | "version": "0.25.0", 415 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", 416 | "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", 417 | "cpu": [ 418 | "arm64" 419 | ], 420 | "dev": true, 421 | "license": "MIT", 422 | "optional": true, 423 | "os": [ 424 | "win32" 425 | ], 426 | "engines": { 427 | "node": ">=18" 428 | } 429 | }, 430 | "node_modules/@esbuild/win32-ia32": { 431 | "version": "0.25.0", 432 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", 433 | "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", 434 | "cpu": [ 435 | "ia32" 436 | ], 437 | "dev": true, 438 | "license": "MIT", 439 | "optional": true, 440 | "os": [ 441 | "win32" 442 | ], 443 | "engines": { 444 | "node": ">=18" 445 | } 446 | }, 447 | "node_modules/@esbuild/win32-x64": { 448 | "version": "0.25.0", 449 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", 450 | "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", 451 | "cpu": [ 452 | "x64" 453 | ], 454 | "dev": true, 455 | "license": "MIT", 456 | "optional": true, 457 | "os": [ 458 | "win32" 459 | ], 460 | "engines": { 461 | "node": ">=18" 462 | } 463 | }, 464 | "node_modules/@scure/base": { 465 | "resolved": "../..", 466 | "link": true 467 | }, 468 | "node_modules/esbuild": { 469 | "version": "0.25.0", 470 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", 471 | "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", 472 | "dev": true, 473 | "hasInstallScript": true, 474 | "license": "MIT", 475 | "bin": { 476 | "esbuild": "bin/esbuild" 477 | }, 478 | "engines": { 479 | "node": ">=18" 480 | }, 481 | "optionalDependencies": { 482 | "@esbuild/aix-ppc64": "0.25.0", 483 | "@esbuild/android-arm": "0.25.0", 484 | "@esbuild/android-arm64": "0.25.0", 485 | "@esbuild/android-x64": "0.25.0", 486 | "@esbuild/darwin-arm64": "0.25.0", 487 | "@esbuild/darwin-x64": "0.25.0", 488 | "@esbuild/freebsd-arm64": "0.25.0", 489 | "@esbuild/freebsd-x64": "0.25.0", 490 | "@esbuild/linux-arm": "0.25.0", 491 | "@esbuild/linux-arm64": "0.25.0", 492 | "@esbuild/linux-ia32": "0.25.0", 493 | "@esbuild/linux-loong64": "0.25.0", 494 | "@esbuild/linux-mips64el": "0.25.0", 495 | "@esbuild/linux-ppc64": "0.25.0", 496 | "@esbuild/linux-riscv64": "0.25.0", 497 | "@esbuild/linux-s390x": "0.25.0", 498 | "@esbuild/linux-x64": "0.25.0", 499 | "@esbuild/netbsd-arm64": "0.25.0", 500 | "@esbuild/netbsd-x64": "0.25.0", 501 | "@esbuild/openbsd-arm64": "0.25.0", 502 | "@esbuild/openbsd-x64": "0.25.0", 503 | "@esbuild/sunos-x64": "0.25.0", 504 | "@esbuild/win32-arm64": "0.25.0", 505 | "@esbuild/win32-ia32": "0.25.0", 506 | "@esbuild/win32-x64": "0.25.0" 507 | } 508 | } 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /test/build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "input.js", 6 | "type": "module", 7 | "devDependencies": { 8 | "@scure/base": "file:../..", 9 | "esbuild": "0.25.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/deno.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertThrows } from 'https://deno.land/std@0.146.0/testing/asserts.ts'; 2 | import { decodeHex } from 'https://deno.land/std/encoding/hex.ts'; 3 | import BASE58_VECTORS from './vectors/base58.json' with { type: 'json' }; 4 | import BASE58_XMR_VECTORS from './vectors/base58_xmr.json' with { type: 'json' }; 5 | import { base58, base58xmr, utils } from '../mod.ts'; 6 | 7 | const hexToArray = (hex: string) => decodeHex(hex); 8 | 9 | Deno.test('deno: base58: vectors', () => { 10 | for (let i = 0; i < BASE58_VECTORS.length; i++) { 11 | const { decodedHex, encoded } = BASE58_VECTORS[i]; 12 | 13 | assertEquals(base58.encode(hexToArray(decodedHex)), encoded, `encode ${i}`); 14 | } 15 | }); 16 | 17 | Deno.test('deno: base58: xmr vectors (valid)', () => { 18 | for (let i = 0; i < BASE58_XMR_VECTORS.validAddrs.length; i++) { 19 | const decAddr = BASE58_XMR_VECTORS.decodedAddrs[i]; 20 | const validAddr = BASE58_XMR_VECTORS.validAddrs[i]; 21 | 22 | assertEquals(base58xmr.encode(hexToArray(decAddr)), validAddr, `encode ${i}`); 23 | assertEquals(base58xmr.decode(validAddr), hexToArray(decAddr), `decode ${i}`); 24 | } 25 | }); 26 | 27 | Deno.test('deno: utils: padding', () => { 28 | const coder = utils.padding(4, '='); 29 | 30 | assertEquals(coder.encode(['1']), ['1', '=']); 31 | 32 | // these check for invalids, so the inputs are meant to be type-unsafe 33 | assertThrows(() => coder.encode(['1', 1, true] as unknown as string[])); 34 | assertThrows(() => coder.decode(['1', 1, true, '='] as unknown as string[])); 35 | }); 36 | -------------------------------------------------------------------------------- /test/generator-rust/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /test/generator-rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "async-stream" 7 | version = "0.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" 10 | dependencies = [ 11 | "async-stream-impl", 12 | "futures-core", 13 | ] 14 | 15 | [[package]] 16 | name = "async-stream-impl" 17 | version = "0.3.2" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" 20 | dependencies = [ 21 | "proc-macro2", 22 | "quote", 23 | "syn", 24 | ] 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.0.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 31 | 32 | [[package]] 33 | name = "base58" 34 | version = "0.2.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" 37 | 38 | [[package]] 39 | name = "base58-monero" 40 | version = "0.3.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "935c90240f9b7749c80746bf88ad9cb346f34b01ee30ad4d566dfdecd6e3cc6a" 43 | dependencies = [ 44 | "async-stream", 45 | "futures-util", 46 | "thiserror", 47 | "tiny-keccak", 48 | "tokio", 49 | ] 50 | 51 | [[package]] 52 | name = "bech32" 53 | version = "0.8.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" 56 | 57 | [[package]] 58 | name = "block-buffer" 59 | version = "0.9.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 62 | dependencies = [ 63 | "generic-array", 64 | ] 65 | 66 | [[package]] 67 | name = "bs58" 68 | version = "0.4.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" 71 | 72 | [[package]] 73 | name = "bytes" 74 | version = "1.1.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 77 | 78 | [[package]] 79 | name = "cfg-if" 80 | version = "1.0.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 83 | 84 | [[package]] 85 | name = "cpufeatures" 86 | version = "0.2.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 89 | dependencies = [ 90 | "libc", 91 | ] 92 | 93 | [[package]] 94 | name = "crunchy" 95 | version = "0.2.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 98 | 99 | [[package]] 100 | name = "darling" 101 | version = "0.13.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" 104 | dependencies = [ 105 | "darling_core", 106 | "darling_macro", 107 | ] 108 | 109 | [[package]] 110 | name = "darling_core" 111 | version = "0.13.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" 114 | dependencies = [ 115 | "fnv", 116 | "ident_case", 117 | "proc-macro2", 118 | "quote", 119 | "strsim", 120 | "syn", 121 | ] 122 | 123 | [[package]] 124 | name = "darling_macro" 125 | version = "0.13.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" 128 | dependencies = [ 129 | "darling_core", 130 | "quote", 131 | "syn", 132 | ] 133 | 134 | [[package]] 135 | name = "data-encoding" 136 | version = "2.3.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" 139 | 140 | [[package]] 141 | name = "digest" 142 | version = "0.9.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 145 | dependencies = [ 146 | "generic-array", 147 | ] 148 | 149 | [[package]] 150 | name = "fnv" 151 | version = "1.0.7" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 154 | 155 | [[package]] 156 | name = "futures-core" 157 | version = "0.3.18" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" 160 | 161 | [[package]] 162 | name = "futures-macro" 163 | version = "0.3.18" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" 166 | dependencies = [ 167 | "proc-macro2", 168 | "quote", 169 | "syn", 170 | ] 171 | 172 | [[package]] 173 | name = "futures-task" 174 | version = "0.3.18" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" 177 | 178 | [[package]] 179 | name = "futures-util" 180 | version = "0.3.18" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" 183 | dependencies = [ 184 | "futures-core", 185 | "futures-macro", 186 | "futures-task", 187 | "pin-project-lite", 188 | "pin-utils", 189 | "slab", 190 | ] 191 | 192 | [[package]] 193 | name = "genTestsBases" 194 | version = "0.1.0" 195 | dependencies = [ 196 | "base58", 197 | "base58-monero", 198 | "bech32", 199 | "bs58", 200 | "data-encoding", 201 | "hex", 202 | "serde", 203 | "serde_json", 204 | "serde_with", 205 | "sha2", 206 | ] 207 | 208 | [[package]] 209 | name = "generic-array" 210 | version = "0.14.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 213 | dependencies = [ 214 | "typenum", 215 | "version_check", 216 | ] 217 | 218 | [[package]] 219 | name = "hex" 220 | version = "0.4.3" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 223 | 224 | [[package]] 225 | name = "ident_case" 226 | version = "1.0.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 229 | 230 | [[package]] 231 | name = "itoa" 232 | version = "0.4.8" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 235 | 236 | [[package]] 237 | name = "libc" 238 | version = "0.2.108" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" 241 | 242 | [[package]] 243 | name = "memchr" 244 | version = "2.4.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 247 | 248 | [[package]] 249 | name = "opaque-debug" 250 | version = "0.3.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 253 | 254 | [[package]] 255 | name = "pin-project-lite" 256 | version = "0.2.7" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 259 | 260 | [[package]] 261 | name = "pin-utils" 262 | version = "0.1.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 265 | 266 | [[package]] 267 | name = "proc-macro2" 268 | version = "1.0.32" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 271 | dependencies = [ 272 | "unicode-xid", 273 | ] 274 | 275 | [[package]] 276 | name = "quote" 277 | version = "1.0.10" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 280 | dependencies = [ 281 | "proc-macro2", 282 | ] 283 | 284 | [[package]] 285 | name = "rustversion" 286 | version = "1.0.5" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 289 | 290 | [[package]] 291 | name = "ryu" 292 | version = "1.0.6" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" 295 | 296 | [[package]] 297 | name = "serde" 298 | version = "1.0.130" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 301 | dependencies = [ 302 | "serde_derive", 303 | ] 304 | 305 | [[package]] 306 | name = "serde_derive" 307 | version = "1.0.130" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 310 | dependencies = [ 311 | "proc-macro2", 312 | "quote", 313 | "syn", 314 | ] 315 | 316 | [[package]] 317 | name = "serde_json" 318 | version = "1.0.72" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" 321 | dependencies = [ 322 | "itoa", 323 | "ryu", 324 | "serde", 325 | ] 326 | 327 | [[package]] 328 | name = "serde_with" 329 | version = "1.11.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3" 332 | dependencies = [ 333 | "hex", 334 | "rustversion", 335 | "serde", 336 | "serde_json", 337 | "serde_with_macros", 338 | ] 339 | 340 | [[package]] 341 | name = "serde_with_macros" 342 | version = "1.5.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" 345 | dependencies = [ 346 | "darling", 347 | "proc-macro2", 348 | "quote", 349 | "syn", 350 | ] 351 | 352 | [[package]] 353 | name = "sha2" 354 | version = "0.9.8" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" 357 | dependencies = [ 358 | "block-buffer", 359 | "cfg-if", 360 | "cpufeatures", 361 | "digest", 362 | "opaque-debug", 363 | ] 364 | 365 | [[package]] 366 | name = "slab" 367 | version = "0.4.5" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 370 | 371 | [[package]] 372 | name = "strsim" 373 | version = "0.10.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 376 | 377 | [[package]] 378 | name = "syn" 379 | version = "1.0.82" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 382 | dependencies = [ 383 | "proc-macro2", 384 | "quote", 385 | "unicode-xid", 386 | ] 387 | 388 | [[package]] 389 | name = "thiserror" 390 | version = "1.0.30" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 393 | dependencies = [ 394 | "thiserror-impl", 395 | ] 396 | 397 | [[package]] 398 | name = "thiserror-impl" 399 | version = "1.0.30" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 402 | dependencies = [ 403 | "proc-macro2", 404 | "quote", 405 | "syn", 406 | ] 407 | 408 | [[package]] 409 | name = "tiny-keccak" 410 | version = "2.0.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 413 | dependencies = [ 414 | "crunchy", 415 | ] 416 | 417 | [[package]] 418 | name = "tokio" 419 | version = "1.14.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" 422 | dependencies = [ 423 | "autocfg", 424 | "bytes", 425 | "memchr", 426 | "pin-project-lite", 427 | ] 428 | 429 | [[package]] 430 | name = "typenum" 431 | version = "1.14.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 434 | 435 | [[package]] 436 | name = "unicode-xid" 437 | version = "0.2.2" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 440 | 441 | [[package]] 442 | name = "version_check" 443 | version = "0.9.3" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 446 | -------------------------------------------------------------------------------- /test/generator-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "genTestsBases" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0", features = ["derive"] } 10 | serde_json = "1.0" 11 | serde_with = { version = "1.11.0", features = ["hex", "json", "macros"] } 12 | sha2 = "0.9.8" 13 | hex = "0.4.3" 14 | bs58 = "0.4.0" 15 | base58-monero = "0.3.2" 16 | base58 = "0.2.0" 17 | data-encoding = "2.3.2" 18 | bech32 = "0.8.1" -------------------------------------------------------------------------------- /test/generator-rust/README.md: -------------------------------------------------------------------------------- 1 | # Test generator for scure-base 2 | ```sh 3 | cargo +nightly run > ../test/vectors/base_vectors.json 4 | ``` 5 | -------------------------------------------------------------------------------- /test/generator-rust/src/bin/genTestsBases.rs: -------------------------------------------------------------------------------- 1 | use base58; 2 | use base58_monero; 3 | use bech32::{self, ToBase32}; 4 | use bs58; 5 | use data_encoding; 6 | use genTestsBases::utils; 7 | use serde::Serialize; 8 | use serde_with::{serde_as, skip_serializing_none}; 9 | 10 | #[serde_as] 11 | #[skip_serializing_none] 12 | #[derive(Serialize)] 13 | struct TestCase { 14 | fn_name: String, 15 | #[serde_as(as = "serde_with::hex::Hex")] 16 | data: Vec, 17 | exp: String, 18 | } 19 | 20 | #[derive(Serialize)] 21 | struct TestCases { 22 | v: Vec, 23 | } 24 | 25 | impl TestCases { 26 | fn new() -> Self { TestCases { v: vec![] } } 27 | 28 | fn base32(&mut self, data: &[u8]) { 29 | let exp = data_encoding::BASE32.encode(data).to_string(); 30 | self.v.push(TestCase { fn_name: "base32".to_string(), data: data.to_vec(), exp }) 31 | } 32 | 33 | fn base32hex(&mut self, data: &[u8]) { 34 | let exp = data_encoding::BASE32HEX.encode(data).to_string(); 35 | self.v.push(TestCase { fn_name: "base32hex".to_string(), data: data.to_vec(), exp }) 36 | } 37 | 38 | fn base58_monero(&mut self, data: &[u8]) { 39 | let exp = base58_monero::encode(data).unwrap(); 40 | self.v.push(TestCase { fn_name: "base58xmr".to_string(), data: data.to_vec(), exp }) 41 | } 42 | 43 | fn base58(&mut self, data: &[u8]) { 44 | let exp = bs58::encode(data).into_string(); 45 | self.v.push(TestCase { fn_name: "base58".to_string(), data: data.to_vec(), exp }) 46 | } 47 | 48 | fn base64(&mut self, data: &[u8]) { 49 | let exp = data_encoding::BASE64.encode(data).to_string(); 50 | self.v.push(TestCase { fn_name: "base64".to_string(), data: data.to_vec(), exp }) 51 | } 52 | 53 | fn base64url(&mut self, data: &[u8]) { 54 | let exp = data_encoding::BASE64URL.encode(data).to_string(); 55 | self.v.push(TestCase { fn_name: "base64url".to_string(), data: data.to_vec(), exp }) 56 | } 57 | 58 | fn bech32(&mut self, data: &[u8]) { 59 | // static prefix, not much reason to test 60 | let exp = bech32::encode("bech32", data.to_base32(), bech32::Variant::Bech32).unwrap(); 61 | self.v.push(TestCase { fn_name: "bech32".to_string(), data: data.to_vec(), exp }) 62 | } 63 | 64 | fn bech32m(&mut self, data: &[u8]) { 65 | // static prefix, not much reason to test 66 | let exp = bech32::encode("bech32m", data.to_base32(), bech32::Variant::Bech32m).unwrap(); 67 | self.v.push(TestCase { fn_name: "bech32m".to_string(), data: data.to_vec(), exp }) 68 | } 69 | 70 | fn add_all(&mut self, data: &[u8]) { 71 | self.bech32(data); 72 | self.bech32m(data); 73 | self.base32(data); 74 | self.base32hex(data); 75 | self.base58_monero(data); 76 | self.base58(data); 77 | self.base64(data); 78 | self.base64url(data); 79 | } 80 | 81 | fn to_json(&self) -> String { serde_json::to_string(&self).unwrap() } 82 | } 83 | 84 | fn main() { 85 | let R1 = utils::random(1, 4096); 86 | 87 | let mut a = TestCases::new(); 88 | for i in 0..512 { 89 | a.add_all(&R1[0..i]); 90 | let zeros = vec![0u8; i]; 91 | a.add_all(&zeros); 92 | let ones = vec![0xffu8; i]; 93 | a.add_all(&ones); 94 | } 95 | // Main idea: test different bit level masks: [left, middle, right], middle is always 0xff (all bits is 1) 96 | // left & right -- kinda sliding window 97 | const edges: [u8; 16] = [ 98 | 0b1111_1111, 99 | 0b0111_1111, 100 | 0b0011_1111, 101 | 0b0001_1111, 102 | 0b0000_1111, 103 | 0b0000_0111, 104 | 0b0000_0011, 105 | 0b0000_0001, 106 | 0b0000_0000, 107 | 0b1000_0000, 108 | 0b1100_0000, 109 | 0b1110_0000, 110 | 0b1111_0000, 111 | 0b1111_1000, 112 | 0b1111_1100, 113 | 0b1111_1110, 114 | ]; 115 | for l in edges { 116 | for r in edges { 117 | let data: [u8; 3] = [l, 0b1111_1111, r]; 118 | a.add_all(&data) 119 | } 120 | } 121 | 122 | println!("{}", a.to_json()); 123 | } 124 | -------------------------------------------------------------------------------- /test/generator-rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | -------------------------------------------------------------------------------- /test/generator-rust/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::min; 2 | 3 | use sha2::{Digest, Sha256, Sha512}; 4 | 5 | pub fn bitsequence(bs: Option<&[u8]>) -> (*const u8, u64) { 6 | let (cust_ptr, cust_len) = if let Some(n) = bs { (n.as_ptr(), 8 * n.len()) } else { ([].as_ptr(), 0) }; 7 | (cust_ptr, cust_len as u64) 8 | } 9 | 10 | pub fn check_output(name: &str, out: i32) { 11 | if out != 0 { 12 | panic!("Failed {}", name); 13 | } 14 | } 15 | 16 | pub fn random(start: u8, len: usize) -> Vec { 17 | let mut out = Vec::with_capacity(len); 18 | 19 | while out.len() < len { 20 | let mut hasher = Sha256::new(); 21 | hasher.update(&[start]); 22 | let result = hasher.finalize(); 23 | out.extend_from_slice(&result[..min(result.len(), len - out.len())]); 24 | } 25 | return out; 26 | } 27 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { should } from 'micro-should'; 2 | 3 | import './bases.test.js'; 4 | import './rfc4648.test.js'; 5 | import './base58.test.js'; 6 | import './bech32.test.js'; 7 | import './bip173.test.js'; 8 | import './utils.test.js'; 9 | 10 | should.runWhen(import.meta.url); 11 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.1.0" 6 | } 7 | -------------------------------------------------------------------------------- /test/rfc4648.test.js: -------------------------------------------------------------------------------- 1 | import { should } from 'micro-should'; 2 | import { deepStrictEqual as eql, throws } from 'node:assert'; 3 | import { Buffer } from 'node:buffer'; 4 | import { base16, base32, base32crockford, base32hex, base64, base64url } from '../lib/esm/index.js'; 5 | 6 | const BASE16_VECTORS = [ 7 | ['', ''], 8 | ['66', '66'], 9 | ['666f', '666F'], 10 | ['666f6f', '666F6F'], 11 | ['666f6f62', '666F6F62'], 12 | ['666f6f6261', '666F6F6261'], 13 | ['666f6f626172', '666F6F626172'], 14 | ['0123456789abcdef', '0123456789ABCDEF'], 15 | ]; 16 | 17 | const BASE16_BAD = ['0', '0=', '00=', 'что', 'M😴']; 18 | 19 | const BASE32_VECTORS = [ 20 | ['', ''], 21 | ['66', 'MY======'], 22 | ['666f', 'MZXQ===='], 23 | ['666f6f', 'MZXW6==='], 24 | ['666f6f62', 'MZXW6YQ='], 25 | ['666f6f6261', 'MZXW6YTB'], 26 | ['666f6f626172', 'MZXW6YTBOI======'], 27 | ['73', 'OM======'], 28 | ['f80c', '7AGA===='], 29 | ['6450', 'MRIA===='], 30 | ['cc91d0', 'ZSI5A==='], 31 | ['6c60c0', 'NRQMA==='], 32 | ['4f6a23', 'J5VCG==='], 33 | ['88b44f18', 'RC2E6GA='], 34 | ['90bad04714', 'SC5NARYU'], 35 | ['e9ef1def8086', '5HXR334AQY======'], 36 | ['83fe3f9c1e9302', 'QP7D7HA6SMBA===='], 37 | ['15aa1f7cafc17cb8', 'CWVB67FPYF6LQ==='], 38 | ['da51d4fed48b4c32dc', '3JI5J7WURNGDFXA='], 39 | ['c4be14228512d7299831', 'YS7BIIUFCLLSTGBR'], 40 | ['2f273c5b5ef04724fab944', 'F4TTYW266BDSJ6VZIQ======'], 41 | ['969da1b80ec2442d2bdd4bdb', 'S2O2DOAOYJCC2K65JPNQ===='], 42 | ['31f5adb50792f549d3714f3f99', 'GH223NIHSL2UTU3RJ47ZS==='], 43 | ['6a654f7a072c29951930700c0a61', 'NJSU66QHFQUZKGJQOAGAUYI='], 44 | ['0fe29d6825ad999e87d9b7cac3589d', 'B7RJ22BFVWMZ5B6ZW7FMGWE5'], 45 | ['0f960ab44e165973a5172ccd294b3412', 'B6LAVNCOCZMXHJIXFTGSSSZUCI======'], 46 | ['325b9fd847a41fb0d485c207a1a5b02dcf', 'GJNZ7WCHUQP3BVEFYID2DJNQFXHQ===='], 47 | ['ddf80ebe21bf1b1e12a64c5cc6a74b5d92dd', '3X4A5PRBX4NR4EVGJROMNJ2LLWJN2==='], 48 | ['c0cae52c6f641ce04a7ee5b9a8fa8ded121bca', 'YDFOKLDPMQOOAST64W42R6UN5UJBXSQ='], 49 | ['872840a355c8c70586f462c9e669ee760cb3537e', 'Q4UEBI2VZDDQLBXUMLE6M2POOYGLGU36'], 50 | ['5773fe22662818a120c5688824c935fe018208a496', 'K5Z74ITGFAMKCIGFNCECJSJV7YAYECFESY======'], 51 | ['416e23abc524d1b85736e2bea6cfecd5192789034a28', 'IFXCHK6FETI3QVZW4K7KNT7M2UMSPCIDJIUA===='], 52 | ['83d2386ebdd7e8e818ec00e3ccd882aa933b905b7e2e44', 'QPJDQ3V527UOQGHMADR4ZWECVKJTXEC3PYXEI==='], 53 | ['a2fa8b881f3b8024f52745763c4ae08ea12bdf8bef1a72f8', 'UL5IXCA7HOACJ5JHIV3DYSXAR2QSXX4L54NHF6A='], 54 | [ 55 | 'b074ae8b9efde0f17f37bccadde006d039997b59c8efb05add', 56 | 'WB2K5C467XQPC7ZXXTFN3YAG2A4ZS62ZZDX3AWW5', 57 | ], 58 | [ 59 | '764fef941aee7e416dc204ae5ab9c5b9ce644567798e6849aea9', 60 | 'OZH67FA25Z7EC3OCASXFVOOFXHHGIRLHPGHGQSNOVE======', 61 | ], 62 | [ 63 | '4995d9811f37f59797d7c3b9b9e5325aa78277415f70f4accf588c', 64 | 'JGK5TAI7G72ZPF6XYO43TZJSLKTYE52BL5YPJLGPLCGA====', 65 | ], 66 | [ 67 | '24f0812ca8eed58374c11a7008f0b262698b72fd2792709208eaacb2', 68 | 'ETYICLFI53KYG5GBDJYAR4FSMJUYW4X5E6JHBEQI5KWLE===', 69 | ], 70 | [ 71 | 'd70692543810d4bf50d81cf44a55801a557a388a341367c7ea077ca306', 72 | '24DJEVBYCDKL6UGYDT2EUVMADJKXUOEKGQJWPR7KA56KGBQ=', 73 | ], 74 | [ 75 | '6e08a89ca36b677ff8fe99e68a1241c8d8cef2570a5f60b6417d2538b30c', 76 | 'NYEKRHFDNNTX76H6THTIUESBZDMM54SXBJPWBNSBPUSTRMYM', 77 | ], 78 | [ 79 | 'f2fc2319bd29457ccd01e8e194ee9bd7e97298b6610df4ab0f3d5baa0b2d7ccf69829edb74edef', 80 | '6L6CGGN5FFCXZTIB5DQZJ3U327UXFGFWMEG7JKYPHVN2UCZNPTHWTAU63N2O33Y=', 81 | ], 82 | ['00443214c74254b635cf84653a56d7c675be77df', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'], 83 | ]; 84 | 85 | const BASE32_BAD = [ 86 | 'A1======', 87 | 'A9======', 88 | 'Aa======', 89 | 'He1l0===', 90 | 'A=======', 91 | 'A7======', 92 | 'AAA=====', 93 | 'AAAAAA==', 94 | 'AA', 95 | 'AA==', 96 | 'AAAA', 97 | 'AAAAA', 98 | 'AAAAAAA', 99 | 'AAAAAAAA========', 100 | 'что', 101 | 'M😴', 102 | ]; 103 | 104 | const BASE32_HEX = [ 105 | ['', ''], 106 | ['66', 'CO======'], 107 | ['666f', 'CPNG===='], 108 | ['666f6f', 'CPNMU==='], 109 | ['666f6f62', 'CPNMUOG='], 110 | ['666f6f6261', 'CPNMUOJ1'], 111 | ['666f6f626172', 'CPNMUOJ1E8======'], 112 | ['00443214c74254b635cf84653a56d7c675be77df', '0123456789ABCDEFGHIJKLMNOPQRSTUV'], 113 | ]; 114 | 115 | const BASE32_CROCKFORD = [ 116 | ['', ''], 117 | ['61', 'C4'], 118 | ['61', 'c4'], 119 | ['74657374', 'EHJQ6X0'], 120 | ['74657374', 'EHJQ6XO'], 121 | ['6c696e7573', 'DHMPWXBK'], 122 | ['6c696e7573', 'DhmPWXbK'], 123 | ['666f6f626172', 'CSQPYRK1E8'], 124 | ['666f6f626172', 'CSQPYRKLE8'], 125 | ['666f6f626172', 'CSQPYRKIE8'], 126 | ]; 127 | 128 | const BASE64_VECTORS = [ 129 | ['', ''], 130 | ['66', 'Zg=='], 131 | ['666f', 'Zm8='], 132 | ['666f6f', 'Zm9v'], 133 | ['666f6f62', 'Zm9vYg=='], 134 | ['666f6f6261', 'Zm9vYmE='], 135 | ['666f6f626172', 'Zm9vYmFy'], 136 | ['68656c6c6f20776f726c64', 'aGVsbG8gd29ybGQ='], 137 | ['00', 'AA=='], 138 | ['0000', 'AAA='], 139 | ['000000', 'AAAA'], 140 | ['00000000', 'AAAAAA=='], 141 | ['0000000000', 'AAAAAAA='], 142 | ['000000000000', 'AAAAAAAA'], 143 | ['6f10a42826d83d78f77673524d41aa9b', 'bxCkKCbYPXj3dnNSTUGqmw=='], 144 | ['d808d57d3d85fec084e52f970e3f8ee63b8fe8e4', '2AjVfT2F/sCE5S+XDj+O5juP6OQ='], 145 | 146 | ['00108310518720928b30d38f41149351559761969b71d79f', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef'], 147 | ['8218a39259a7a29aabb2dbafc31cb3d35db7e39ebbf3dfbf', 'ghijklmnopqrstuvwxyz0123456789+/'], 148 | ['fbff', '+/8='], 149 | ]; 150 | 151 | const BASE64_BAD = ['A===', 'AA=', 'AAAA====', 'Zg===', 'AAA', '=Zm8', 'что', 'M😴']; 152 | // These throw in scure-base impl, but don't throw in built-in impl 153 | const BASE64_BAD_NON_NATIVE = ['+/+=', 'AAAAA', '=', '==']; 154 | 155 | const BASE64_URL = [['fbff', '-_8=']]; 156 | 157 | function genTests(name, coder, VECTORS, BAD_VECTORS, encode = true) { 158 | for (const [hex, expected] of VECTORS) { 159 | if (encode) { 160 | should(`encode ${name} ${hex}`, () => { 161 | eql(coder.encode(new Uint8Array(Buffer.from(hex, 'hex'))), expected); 162 | }); 163 | } 164 | should(`decode ${name}: ${hex}`, () => { 165 | eql(coder.decode(expected), new Uint8Array(Buffer.from(hex, 'hex'))); 166 | }); 167 | // X=decode(encode(X)) 168 | should(`encode/decode ${name}: ${hex}`, () => { 169 | const hexBytes = new Uint8Array(Buffer.from(hex, 'hex')); 170 | eql(coder.decode(coder.encode(hexBytes)), hexBytes); 171 | }); 172 | } 173 | if (BAD_VECTORS) { 174 | should(`${name}: throw on decode bad vectors`, () => { 175 | for (let v of BAD_VECTORS) throws(() => coder.decode(v)); 176 | }); 177 | } 178 | } 179 | 180 | genTests('base16', base16, BASE16_VECTORS, BASE16_BAD); 181 | genTests('base32', base32, BASE32_VECTORS, BASE32_BAD); 182 | genTests('base32hex', base32hex, BASE32_HEX); 183 | genTests('base32crockford', base32crockford, BASE32_CROCKFORD, undefined, false); 184 | genTests('base64', base64, BASE64_VECTORS, BASE64_BAD); 185 | genTests('base64url', base64url, BASE64_URL); 186 | 187 | should.runWhen(import.meta.url); 188 | -------------------------------------------------------------------------------- /test/slow-dos.test.js: -------------------------------------------------------------------------------- 1 | import { should } from 'micro-should'; 2 | import { deepStrictEqual, rejects } from 'node:assert'; 3 | import { CODERS } from './bases.test.js'; 4 | import { RANDOM, stats } from './utils.js'; 5 | 6 | const getTime = () => Number(process.hrtime.bigint()); 7 | 8 | // Median execution time for callback (reduce noise) 9 | async function bench(callback, iters = 10) { 10 | const timings = []; 11 | for (let i = 0; i < iters; i++) { 12 | const ts = getTime(); 13 | let val = callback(); 14 | if (val instanceof Promise) await val; 15 | timings.push(getTime() - ts); 16 | } 17 | return stats(timings).median; 18 | } 19 | // Handle flaky tests. If complexity test passed even 1 of 5 attempts, then its ok. 20 | // Only when all attempts failed, test is failed. 21 | const retry = 22 | (callback, retries = 5) => 23 | async () => { 24 | for (let i = 0; i < retries - 1; i++) { 25 | try { 26 | return await callback(); 27 | } catch (e) {} 28 | } 29 | // last attempt, throw exception if failed 30 | return await callback(); 31 | }; 32 | 33 | // O(N) 34 | function linear(buf) { 35 | for (let i = 0; i < buf.length; i++); 36 | } 37 | // O(128*1024*N) 38 | function linearConst(buf) { 39 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < 16 * 1024; j++); 40 | } 41 | // O(N*log2(N)) 42 | function log2(buf) { 43 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < Math.log2(buf.length); j++); 44 | } 45 | // O(N*log10(N)) 46 | function log10(buf) { 47 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < Math.log10(buf.length); j++); 48 | } 49 | // O(N^2) 50 | function quadratic(buf) { 51 | for (let i = 0; i < buf.length; i++) for (let j = 0; j < buf.length; j++); 52 | } 53 | // Should be around 0.1, but its significantly depends on environment, GC, other processes that run in parallel. Which makes tests too flaky. 54 | const MARGIN = (() => { 55 | const timings = []; 56 | for (let i = 0; i < 5; i++) { 57 | let ts = getTime(); 58 | linearConst(1024); 59 | timings.push((getTime() - ts) / 1024); 60 | } 61 | const diff = Math.max(...stats(timings).difference.map((i) => Math.abs(i))); 62 | return Math.max(1, diff); 63 | })(); 64 | 65 | console.log(`Time margin: ${MARGIN}`); 66 | 67 | const SMALL_BUF = new Uint8Array(1024); 68 | // Check that there is linear relation between input size and running time of callback 69 | async function isLinear(callback, iters = 128) { 70 | // Warmup && trigger JIT 71 | for (let i = 0; i < 1024; i++) await callback(SMALL_BUF); 72 | // Measure difference between relative execution time (per byte) 73 | const timings = []; 74 | for (let i = 1; i < iters; i++) { 75 | const buf = RANDOM.subarray(0, 1024 * i); 76 | const time = await bench(() => callback(buf)); 77 | timings.push(time / buf.length); // time per byte 78 | } 79 | // Median of differences. Should be close to zero for linear functions (+/- some noise). 80 | const medianDifference = stats(stats(timings.map((i) => i)).difference).median; 81 | console.log({ medianDifference }); 82 | deepStrictEqual( 83 | medianDifference < MARGIN, 84 | true, 85 | `medianDifference(${medianDifference}) should be less than ${MARGIN}` 86 | ); 87 | } 88 | 89 | // Verify that it correctly detects functions with quadratic complexity 90 | should( 91 | 'detect quadratic functions', 92 | retry(async () => { 93 | // 16 iters since quadratic is very slow 94 | console.log('Linear'); 95 | await isLinear((buf) => linear(buf), 16); 96 | console.log('Linear const'); 97 | await isLinear((buf) => linearConst(buf), 16); 98 | // Very close to linear, not much impact 99 | console.log('Log2'); 100 | await isLinear((buf) => log2(buf), 16); 101 | console.log('Log10'); 102 | await isLinear((buf) => log10(buf), 16); 103 | console.log('Quadratic'); 104 | await rejects(() => isLinear((buf) => quadratic(buf), 16)); 105 | }) 106 | ); 107 | 108 | for (const coder in CODERS) { 109 | if (coder.startsWith('base58')) { 110 | should( 111 | `DoS: ${coder} is quadratic :(`, 112 | retry(async () => { 113 | await rejects(() => isLinear((buf) => CODERS[coder].decode(CODERS[coder].encode(buf)), 16)); 114 | }) 115 | ); 116 | } else { 117 | should( 118 | `DoS: ${coder}`, 119 | retry(async () => await isLinear((buf) => CODERS[coder].decode(CODERS[coder].encode(buf)))) 120 | ); 121 | } 122 | } 123 | 124 | // takes ~8min 125 | should.runWhen(import.meta.url); 126 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["./test.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha2'; 2 | import { readFileSync } from 'node:fs'; 3 | import { gunzipSync } from 'node:zlib'; 4 | import { dirname, join as joinPath } from 'node:path'; 5 | import { fileURLToPath } from 'node:url'; 6 | 7 | export const _dirname = dirname(fileURLToPath(import.meta.url)); 8 | 9 | export function jsonGZ(path) { 10 | const unz = gunzipSync(readFileSync(joinPath(_dirname, path))); 11 | return JSON.parse(unz.toString('utf8')); 12 | } 13 | 14 | export function json(path) { 15 | try { 16 | // Node.js 17 | return JSON.parse(readFileSync(joinPath(_dirname, path), { encoding: 'utf-8' })); 18 | } catch (error) { 19 | // Bundler 20 | const file = path.replace(/^\.\//, '').replace(/\.json$/, ''); 21 | if (path !== './' + file + '.json') throw new Error('Can not load non-json file'); 22 | // return require('./' + file + '.json'); // in this form so that bundler can glob this 23 | } 24 | } 25 | 26 | function median(list) { 27 | const values = list.slice().sort((a, b) => a - b); 28 | const half = (values.length / 2) | 0; 29 | return values.length % 2 ? values[half] : (values[half - 1] + values[half]) / 2.0; 30 | } 31 | 32 | function stats(list) { 33 | let [min, max, cnt, sum, absSum] = [+Infinity, -Infinity, 0, 0, 0]; 34 | for (let value of list) { 35 | const num = Number(value); 36 | min = Math.min(min, num); 37 | max = Math.max(max, num); 38 | cnt++; 39 | sum += num; 40 | absSum += Math.abs(num); 41 | } 42 | const sumDiffPercent = (absSum / sum) * 100; 43 | const difference = []; 44 | for (let i = 1; i < list.length; i++) difference.push(list[i] - list[i - 1]); 45 | return { 46 | min, 47 | max, 48 | avg: sum / cnt, 49 | sum, 50 | median: median(list), 51 | absSum, 52 | cnt, 53 | sumDiffPercent, 54 | difference, 55 | }; 56 | } 57 | 58 | // Random data, by using hash we trying to achieve uniform distribution of each byte values 59 | let start = new Uint8Array([1, 2, 3, 4, 5]); 60 | let RANDOM = new Uint8Array(); 61 | // Fill with random data (1MB) 62 | function concatBytes(...arrays) { 63 | let sum = 0; 64 | for (let i = 0; i < arrays.length; i++) { 65 | const a = arrays[i]; 66 | sum += a.length; 67 | } 68 | const res = new Uint8Array(sum); 69 | for (let i = 0, pad = 0; i < arrays.length; i++) { 70 | const a = arrays[i]; 71 | res.set(a, pad); 72 | pad += a.length; 73 | } 74 | return res; 75 | } 76 | 77 | for (let i = 0; i < 32 * 1024; i++) RANDOM = concatBytes(RANDOM, (start = sha256(start))); 78 | 79 | const getTypeTests = () => [ 80 | [0, '0'], 81 | [123, '123'], 82 | [123.456, '123.456'], 83 | [-5n, '-5n'], 84 | [1.0000000000001, '1.0000000000001'], 85 | [10e9999, '10e9999'], 86 | [Infinity, 'Infinity'], 87 | [-Infinity, '-Infinity'], 88 | [NaN, 'NaN'], 89 | [true, 'true'], 90 | [false, 'false'], 91 | [null, 'null'], 92 | [undefined, 'undefined'], 93 | ['', '""'], 94 | ['1', '"1"'], 95 | ['1 ', '"1 "'], 96 | [' 1', '" 1"'], 97 | ['0xbe', '"0xbe"'], 98 | ['keys', '"keys"'], 99 | [new String('1234'), 'String(1234)'], 100 | [new Uint8Array([]), 'ui8a([])'], 101 | [new Uint8Array([0]), 'ui8a([0])'], 102 | [new Uint8Array([1]), 'ui8a([1])'], 103 | // [new Uint8Array(32).fill(1), 'ui8a(32*[1])'], 104 | [new Uint8Array(4096).fill(1), 'ui8a(4096*[1])'], 105 | [new Uint16Array(32).fill(1), 'ui16a(32*[1])'], 106 | [new Uint32Array(32).fill(1), 'ui32a(32*[1])'], 107 | [new Float32Array(32), 'f32a(32*0)'], 108 | [new BigUint64Array(32).fill(1n), 'ui64a(32*[1])'], 109 | [new ArrayBuffer(100), 'arraybuf'], 110 | [new DataView(new ArrayBuffer(100)), 'dataview'], 111 | [{ constructor: { name: 'Uint8Array' }, length: '1e30' }, 'fake(ui8a)'], 112 | [Array(32).fill(1), 'array'], 113 | [new Set([1, 2, 3]), 'set'], 114 | [new Map([['aa', 'bb']]), 'map'], 115 | [() => {}, 'fn'], 116 | [async () => {}, 'fn async'], 117 | [class Test {}, 'class'], 118 | [Symbol.for('a'), 'symbol("a")'], 119 | ]; 120 | 121 | export { concatBytes, RANDOM, stats, getTypeTests }; 122 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | import fc from 'fast-check'; 2 | import { describe, should } from 'micro-should'; 3 | import { deepStrictEqual, throws } from 'node:assert'; 4 | import { hex } from '../lib/esm/index.js'; 5 | import { getTypeTests } from './utils.js'; 6 | 7 | function hexa() { 8 | const items = '0123456789abcdef'; 9 | return fc.integer({ min: 0, max: 15 }).map((n) => items[n]); 10 | } 11 | function hexaString(constraints = {}) { 12 | return fc.string({ ...constraints, unit: hexa() }); 13 | } 14 | 15 | // const concatBytes = utils.concatBytes; 16 | const hexToBytes = hex.decode; 17 | const bytesToHex = hex.encode; 18 | 19 | describe('utils', () => { 20 | const staticHexVectors = [ 21 | { bytes: Uint8Array.from([]), hex: '' }, 22 | { bytes: Uint8Array.from([0xbe]), hex: 'be' }, 23 | { bytes: Uint8Array.from([0xca, 0xfe]), hex: 'cafe' }, 24 | { bytes: Uint8Array.from(new Array(1024).fill(0x69)), hex: '69'.repeat(1024) }, 25 | ]; 26 | should('hexToBytes', () => { 27 | for (let v of staticHexVectors) deepStrictEqual(hexToBytes(v.hex), v.bytes); 28 | for (let v of staticHexVectors) deepStrictEqual(hexToBytes(v.hex.toUpperCase()), v.bytes); 29 | for (let [v, repr] of getTypeTests()) { 30 | if (repr === '""') continue; 31 | throws(() => hexToBytes(v)); 32 | } 33 | }); 34 | should('bytesToHex', () => { 35 | for (let v of staticHexVectors) deepStrictEqual(bytesToHex(v.bytes), v.hex); 36 | for (let [v, repr] of getTypeTests()) { 37 | if (repr.startsWith('ui8a')) continue; 38 | throws(() => bytesToHex(v)); 39 | } 40 | }); 41 | should('hexToBytes <=> bytesToHex roundtrip', () => 42 | fc.assert( 43 | fc.property(hexaString({ minLength: 2, maxLength: 64 }), (hex) => { 44 | if (hex.length % 2 !== 0) return; 45 | deepStrictEqual(hex, bytesToHex(hexToBytes(hex))); 46 | deepStrictEqual(hex, bytesToHex(hexToBytes(hex.toUpperCase()))); 47 | if (typeof Buffer !== 'undefined') 48 | deepStrictEqual(hexToBytes(hex), Uint8Array.from(Buffer.from(hex, 'hex'))); 49 | }) 50 | ) 51 | ); 52 | // should('concatBytes', () => { 53 | // const a = 1; 54 | // const b = 2; 55 | // const c = 0xff; 56 | // const aa = Uint8Array.from([a]); 57 | // const bb = Uint8Array.from([b]); 58 | // const cc = Uint8Array.from([c]); 59 | // deepStrictEqual(concatBytes(), new Uint8Array()); 60 | // deepStrictEqual(concatBytes(aa, bb), Uint8Array.from([a, b])); 61 | // deepStrictEqual(concatBytes(aa, bb, cc), Uint8Array.from([a, b, c])); 62 | // for (let [v, repr] of getTypeTests()) { 63 | // if (repr.startsWith('ui8a')) continue; 64 | // throws(() => { 65 | // concatBytes(v); 66 | // }); 67 | // throws(() => { 68 | // concatBytes(aa, v); 69 | // }); 70 | // } 71 | // }); 72 | // should('concatBytes random', () => 73 | // fc.assert( 74 | // fc.property(fc.uint8Array(), fc.uint8Array(), fc.uint8Array(), (a, b, c) => { 75 | // const expected = Uint8Array.from(Buffer.concat([a, b, c])); 76 | // deepStrictEqual(concatBytes(a.slice(), b.slice(), c.slice()), expected); 77 | // }) 78 | // ) 79 | // ); 80 | }); 81 | 82 | // ESM is broken. 83 | // import url from 'node:url'; 84 | // if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { 85 | // should.run(); 86 | // } 87 | should.runWhen(import.meta.url); 88 | -------------------------------------------------------------------------------- /test/vectors/base58.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "decodedHex": "", 4 | "encoded": "" 5 | }, 6 | { 7 | "decodedHex": "61", 8 | "encoded": "2g" 9 | }, 10 | { 11 | "decodedHex": "626262", 12 | "encoded": "a3gV" 13 | }, 14 | { 15 | "decodedHex": "636363", 16 | "encoded": "aPEr" 17 | }, 18 | { 19 | "decodedHex": "73696d706c792061206c6f6e6720737472696e67", 20 | "encoded": "2cFupjhnEsSn59qHXstmK2ffpLv2" 21 | }, 22 | { 23 | "decodedHex": "00eb15231dfceb60925886b67d065299925915aeb172c06647", 24 | "encoded": "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L" 25 | }, 26 | { 27 | "decodedHex": "516b6fcd0f", 28 | "encoded": "ABnLTmg" 29 | }, 30 | { 31 | "decodedHex": "bf4f89001e670274dd", 32 | "encoded": "3SEo3LWLoPntC" 33 | }, 34 | { 35 | "decodedHex": "572e4794", 36 | "encoded": "3EFU7m" 37 | }, 38 | { 39 | "decodedHex": "ecac89cad93923c02321", 40 | "encoded": "EJDM8drfXA6uyA" 41 | }, 42 | { 43 | "decodedHex": "10c8511e", 44 | "encoded": "Rt5zm" 45 | }, 46 | { 47 | "decodedHex": "00000000000000000000", 48 | "encoded": "1111111111" 49 | }, 50 | { 51 | "decodedHex": "801184cd2cdd640ca42cfc3a091c51d549b2f016d454b2774019c2b2d2e08529fd206ec97e", 52 | "encoded": "5Hx15HFGyep2CfPxsJKe2fXJsCVn5DEiyoeGGF6JZjGbTRnqfiD" 53 | }, 54 | { 55 | "decodedHex": "003c176e659bea0f29a3e9bf7880c112b1b31b4dc826268187", 56 | "encoded": "16UjcYNBG9GTK4uq2f7yYEbuifqCzoLMGS" 57 | } 58 | ] 59 | 60 | -------------------------------------------------------------------------------- /test/vectors/base58_check.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid": [ 3 | { 4 | "string": "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", 5 | "payload": "0065a16059864a2fdbc7c99a4723a8395bc6f188eb" 6 | }, 7 | { 8 | "string": "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", 9 | "payload": "0574f209f6ea907e2ea48f74fae05782ae8a665257" 10 | }, 11 | { 12 | "string": "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 13 | "payload": "6f53c0307d6851aa0ce7825ba883c6bd9ad242b486" 14 | }, 15 | { 16 | "string": "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 17 | "payload": "c46349a418fc4578d10a372b54b45c280cc8c4382f" 18 | }, 19 | { 20 | "string": "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr", 21 | "payload": "80eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19" 22 | }, 23 | { 24 | "string": "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD", 25 | "payload": "8055c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c401" 26 | }, 27 | { 28 | "string": "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko", 29 | "payload": "ef36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2" 30 | }, 31 | { 32 | "string": "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH", 33 | "payload": "efb9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f301" 34 | }, 35 | { 36 | "string": "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", 37 | "payload": "006d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4" 38 | }, 39 | { 40 | "string": "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", 41 | "payload": "05fcc5460dd6e2487c7d75b1963625da0e8f4c5975" 42 | }, 43 | { 44 | "string": "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ", 45 | "payload": "6ff1d470f9b02370fdec2e6b708b08ac431bf7a5f7" 46 | }, 47 | { 48 | "string": "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", 49 | "payload": "c4c579342c2c4c9220205e2cdc285617040c924a0a" 50 | }, 51 | { 52 | "string": "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc", 53 | "payload": "80a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e" 54 | }, 55 | { 56 | "string": "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi", 57 | "payload": "807d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb401" 58 | }, 59 | { 60 | "string": "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj", 61 | "payload": "efd6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203" 62 | }, 63 | { 64 | "string": "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN", 65 | "payload": "efa81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d901" 66 | }, 67 | { 68 | "string": "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", 69 | "payload": "007987ccaa53d02c8873487ef919677cd3db7a6912" 70 | }, 71 | { 72 | "string": "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", 73 | "payload": "0563bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb" 74 | }, 75 | { 76 | "string": "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk", 77 | "payload": "6fef66444b5b17f14e8fae6e7e19b045a78c54fd79" 78 | }, 79 | { 80 | "string": "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o", 81 | "payload": "c4c3e55fceceaa4391ed2a9677f4a4d34eacd021a0" 82 | }, 83 | { 84 | "string": "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9", 85 | "payload": "80e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252" 86 | }, 87 | { 88 | "string": "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT", 89 | "payload": "808248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c01" 90 | }, 91 | { 92 | "string": "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo", 93 | "payload": "ef44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52" 94 | }, 95 | { 96 | "string": "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7", 97 | "payload": "efd1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c6901" 98 | }, 99 | { 100 | "string": "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", 101 | "payload": "00adc1cc2081a27206fae25792f28bbc55b831549d" 102 | }, 103 | { 104 | "string": "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", 105 | "payload": "05188f91a931947eddd7432d6e614387e32b244709" 106 | }, 107 | { 108 | "string": "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H", 109 | "payload": "6f1694f5bc1a7295b600f40018a618a6ea48eeb498" 110 | }, 111 | { 112 | "string": "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN", 113 | "payload": "c43b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3" 114 | }, 115 | { 116 | "string": "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR", 117 | "payload": "80091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0" 118 | }, 119 | { 120 | "string": "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8", 121 | "payload": "80ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af01" 122 | }, 123 | { 124 | "string": "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq", 125 | "payload": "efb4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856" 126 | }, 127 | { 128 | "string": "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA", 129 | "payload": "efe7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef01" 130 | }, 131 | { 132 | "string": "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", 133 | "payload": "00c4c1b72491ede1eedaca00618407ee0b772cad0d" 134 | }, 135 | { 136 | "string": "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", 137 | "payload": "05f6fe69bcb548a829cce4c57bf6fff8af3a5981f9" 138 | }, 139 | { 140 | "string": "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6", 141 | "payload": "6f261f83568a098a8638844bd7aeca039d5f2352c0" 142 | }, 143 | { 144 | "string": "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda", 145 | "payload": "c4e930e1834a4d234702773951d627cce82fbb5d2e" 146 | }, 147 | { 148 | "string": "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg", 149 | "payload": "80d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0" 150 | }, 151 | { 152 | "string": "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi", 153 | "payload": "80b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b301" 154 | }, 155 | { 156 | "string": "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys", 157 | "payload": "ef037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb" 158 | }, 159 | { 160 | "string": "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw", 161 | "payload": "ef6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de01" 162 | }, 163 | { 164 | "string": "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", 165 | "payload": "005eadaf9bb7121f0f192561a5a62f5e5f54210292" 166 | }, 167 | { 168 | "string": "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", 169 | "payload": "053f210e7277c899c3a155cc1c90f4106cbddeec6e" 170 | }, 171 | { 172 | "string": "myoqcgYiehufrsnnkqdqbp69dddVDMopJu", 173 | "payload": "6fc8a3c2a09a298592c3e180f02487cd91ba3400b5" 174 | }, 175 | { 176 | "string": "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C", 177 | "payload": "c499b31df7c9068d1481b596578ddbb4d3bd90baeb" 178 | }, 179 | { 180 | "string": "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4", 181 | "payload": "80c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae" 182 | }, 183 | { 184 | "string": "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2", 185 | "payload": "8007f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd01" 186 | }, 187 | { 188 | "string": "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV", 189 | "payload": "efea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801" 190 | }, 191 | { 192 | "string": "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h", 193 | "payload": "ef0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c01" 194 | }, 195 | { 196 | "string": "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", 197 | "payload": "001ed467017f043e91ed4c44b4e8dd674db211c4e6" 198 | }, 199 | { 200 | "string": "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", 201 | "payload": "055ece0cadddc415b1980f001785947120acdb36fc" 202 | } 203 | ], 204 | "invalid": [ 205 | { 206 | "string": "Z9inZq4e2HGQRZQezDjFMmqgUE8NwMRok", 207 | "exception": "Invalid checksum" 208 | }, 209 | { 210 | "string": "3HK7MezAm6qEZQUMPRf8jX7wDv6zig6Ky8", 211 | "exception": "Invalid checksum" 212 | }, 213 | { 214 | "string": "3AW8j12DUk8mgA7kkfZ1BrrzCVFuH1LsXS", 215 | "exception": "Invalid checksum" 216 | }, 217 | { 218 | "string": "#####", 219 | "exception": "Non-base58 character" 220 | } 221 | ] 222 | } -------------------------------------------------------------------------------- /test/vectors/base58_check.json.LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Daniel Cousens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/vectors/base58_xmr.json: -------------------------------------------------------------------------------- 1 | { 2 | "validAddrs": [ 3 | "4495qPNxDGAT241zya1WdwG5YU6RQ6s7ZgGeQryFtooAMMxoqx2N2oNTjP5NTqDf9GMaZ52iS2Q6xhnWyW16cdi47MCsVRg", 4 | "47Mov77LGqgRoRh6K6XVheSagWVRS7jkQLCR9jPQxTa8g2SrnwbWuMzKWRLyyBFsxn7gHJv15987MDMkYXCXGGvhKA7Qsx4", 5 | "48fj5P3zky9FETVG144GWh2oxnEdBc45VFHLKgKQfZ7UdyJ5M7mDFxuEA12eBuD55RAwgX2jzFYfwjhukHavcLHW9vKn1VG", 6 | "48vTj54ZtU7e6sqwcJY9uq2LApd3Zi6H23vmYFc3wMteS2QzJwi2Z1xCLVwMac55h2HnQAiYwZTceJbwMZJRrm3uNh76Hci", 7 | "48oYzqzeGqY3Nfg6LG8HwS3uF1Y3vV2gfRH6ZMcnhhEmUgkL2mPSjtuSekenrYGkbp8RNvAvrtq3r7Ze4iPoBH3kFK9vbgP" 8 | ], 9 | "decodedAddrs": [ 10 | "12426a2b555065c79b8d6293c5dd18c25a25bae1a8b8c67ceac7484133e6798579bba3444bd48d5d9fcffa64d805e3977b07e2d420a2212df3d612a5dbcc67653844ded707", 11 | "12975e989ae39b7b9445ac7384edb7a598efe3fbfab6c0bd72c5372fadd86071e95096d3b5eedd396ea5c521456640fb27ebb5a222269eac49e1ddac7134735ea0efb2b899", 12 | "12b9e8cd1f42a48c55166f75ead8293e0ad1c420f566b9c85562572936207557dd08613f96d197024ea651e8f226feb03b71aa82f487cb6eff518a30a3b6a2514f0eb176af", 13 | "12c09d10f3c5f580ddd0765063d9246007f45ef025a76c7d117fe4e811fa78f3959c66f7487c1bef43c64ee0ace763116456666a389eea3b693cd7670c3515a0c043794fbf", 14 | "12bd785822c5e8330e30cc7e6e7abd3d11579da04e4131d091255172583059aea58501a7d7657332995b54357cc02c972c5cf5b2d1804d4d273c6f214854c9cf7edd34d73c" 15 | ] 16 | } -------------------------------------------------------------------------------- /test/vectors/base58_xmr.json.LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016 The MoneroPy Developers. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@paulmillr/jsbt/tsconfig.cjs.json", 3 | "compilerOptions": { 4 | "outDir": "lib" 5 | }, 6 | "include": ["index.ts", "src"], 7 | "exclude": ["node_modules", "lib"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@paulmillr/jsbt/tsconfig.esm.json", 3 | "compilerOptions": { 4 | "module": "es2020", 5 | "moduleResolution": "bundler", 6 | "noUncheckedIndexedAccess": true, 7 | "outDir": "./lib/esm" 8 | }, 9 | "include": ["index.ts", "src"], 10 | "exclude": ["node_modules", "lib"] 11 | } 12 | --------------------------------------------------------------------------------