├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── husky │ └── commit-msg └── workflows │ ├── pull_request.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── address.ts ├── base58check.ts ├── checksum.ts ├── encoding.ts └── index.ts ├── tests ├── tsconfig.json └── unitTests │ ├── data │ └── random.json │ └── src │ ├── index.ts │ └── tape-promise.d.ts ├── tsconfig.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*.{js,ts,json}] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stacks-network/c32check/f397548d40e32ff31a77628883abfdad48b697f0/.eslintignore -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@stacks/eslint-config", 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "project": [ 10 | "tsconfig.json", 11 | "tests/tsconfig.json" 12 | ] 13 | }, 14 | "env": { 15 | "browser": false, 16 | "node": true 17 | }, 18 | "rules": { 19 | "@typescript-eslint/ban-types": 0, 20 | "@typescript-eslint/adjacent-overload-signatures": 0, 21 | "@typescript-eslint/explicit-module-boundary-types": 0, 22 | "@typescript-eslint/prefer-regexp-exec": 0, 23 | "@typescript-eslint/no-inferrable-types": 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit 5 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull request 2 | 3 | on: [pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | pre_run: 7 | name: Cancel previous runs 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Cancel Previous Runs 11 | uses: styfle/cancel-workflow-action@ad6cb1b847ffb509a69b745b6ee2f1d14dfe14b8 12 | with: 13 | access_token: ${{ github.token }} 14 | 15 | code_checks: 16 | name: Code checks 17 | runs-on: ubuntu-latest 18 | needs: pre_run 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: 16 24 | cache: npm 25 | - run: npm ci 26 | 27 | - run: npm run data-set-test 28 | 29 | - name: Code Checks 30 | run: npm run prepublishOnly 31 | 32 | - run: npm run codecovUpload 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | paths-ignore: 9 | - "**.json" 10 | - "**.md" 11 | 12 | workflow_dispatch: 13 | 14 | jobs: 15 | pre_run: 16 | name: Cancel previous runs 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Cancel Previous Runs 20 | uses: styfle/cancel-workflow-action@ad6cb1b847ffb509a69b745b6ee2f1d14dfe14b8 21 | with: 22 | access_token: ${{ github.token }} 23 | 24 | release: 25 | name: Release 26 | runs-on: ubuntu-latest 27 | 28 | if: "github.event_name == 'push' && contains(github.event.head_commit.message, 'chore(release): publish')" 29 | needs: 30 | - pre_run 31 | 32 | steps: 33 | - uses: actions/checkout@v3 34 | with: 35 | token: ${{ secrets.GH_TOKEN }} 36 | 37 | - name: Semantic Release 38 | uses: cycjimmy/semantic-release-action@v3 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 41 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | SEMANTIC_RELEASE_PACKAGE: ${{ github.event.repository.name }} 43 | with: 44 | extra_plugins: | 45 | @semantic-release/commit-analyzer 46 | @semantic-release/changelog 47 | @semantic-release/exec 48 | @semantic-release/git 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # vim 64 | *.swp 65 | *.swo 66 | 67 | # compiled files 68 | lib/* 69 | tests/unitTests/lib/* 70 | dist/* 71 | 72 | # os files 73 | .DS_Store 74 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | unused 3 | .nyc_output 4 | coverage -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Test", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeArgs": ["-r", "ts-node/register/transpile-only"], 10 | "args": ["${workspaceRoot}/tests/unitTests/src/index.ts"] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jude Nelson 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # c32check 2 | 3 | [Crockford base-32](https://en.wikipedia.org/wiki/Base32#Crockford's_Base32) 4 | encoding library with 4-byte checksum. 5 | 6 | This library is meant for generating and decoding addresses on the Stacks 7 | blockchain. 8 | 9 | ## How it works 10 | 11 | Each c32check string encodes a 1-byte version and a 4-byte checksum. When 12 | decoded as a hex string, the wire format looks like this: 13 | 14 | ``` 15 | 0 1 n+1 n+5 16 | |------|-----------------------------|---------------| 17 | version n-byte hex payload 4-byte hash 18 | ``` 19 | 20 | If `version` is the version byte (a 1-byte `number`) and `payload` is the raw 21 | bytes (e.g. as a `string`), then the `checksum` is calculated as follows: 22 | 23 | ``` 24 | checksum = sha256(sha256(version + payload)).substring(0,4) 25 | ``` 26 | 27 | In other words, the checksum is the first four bytes of the 28 | double-sha256 of the bytestring concatenation of the `version` and `payload`. 29 | This is similar to base58check encoding, for example. 30 | 31 | ## c32 Addresses 32 | 33 | The Stacks blockchain uses c32-encoded public key hashes as addresses. 34 | Specifically, a **c32check address** is a c32check-encoded ripemd160 hash. 35 | 36 | --- 37 | 38 | # Examples 39 | 40 | ``` 41 | > c32 = require('c32check') 42 | { c32encode: [Function: c32encode], 43 | c32decode: [Function: c32decode], 44 | c32checkEncode: [Function: c32checkEncode], 45 | c32checkDecode: [Function: c32checkDecode], 46 | c32address: [Function: c32address], 47 | c32addressDecode: [Function: c32addressDecode], 48 | versions: 49 | { mainnet: { p2pkh: 22, p2sh: 20 }, 50 | testnet: { p2pkh: 26, p2sh: 21 } }, 51 | c32ToB58: [Function: c32ToB58], 52 | b58ToC32: [Function: b58ToC32] } 53 | ``` 54 | 55 | ## c32encode, c32decode 56 | 57 | ``` 58 | > c32check.c32encode(Buffer.from('hello world').toString('hex')) 59 | '38CNP6RVS0EXQQ4V34' 60 | > c32check.c32decode('38CNP6RVS0EXQQ4V34') 61 | '68656c6c6f20776f726c64' 62 | > Buffer.from('68656c6c6f20776f726c64', 'hex').toString() 63 | 'hello world' 64 | ``` 65 | 66 | ## c32checkEncode, c32checkDecode 67 | 68 | ``` 69 | > version = 12 70 | 12 71 | > c32check.c32checkEncode(version, Buffer.from('hello world').toString('hex')) 72 | 'CD1JPRV3F41VPYWKCCGRMASC8' 73 | > c32check.c32checkDecode('CD1JPRV3F41VPYWKCCGRMASC8') 74 | [ 12, '68656c6c6f20776f726c64' ] 75 | > Buffer.from('68656c6c6f20776f726c64', 'hex').toString() 76 | 'hello world' 77 | ``` 78 | 79 | ## c32address, c32addressDecode 80 | 81 | > **Note**: 82 | > These methods only work on ripemd160 hashes 83 | 84 | ``` 85 | > hash160 = 'a46ff88886c2ef9762d970b4d2c63678835bd39d' 86 | 'a46ff88886c2ef9762d970b4d2c63678835bd39d' 87 | > version = c32check.versions.mainnet.p2pkh 88 | 22 89 | > c32check.c32address(version, hash160) 90 | 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7' 91 | > c32check.c32addressDecode('SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7') 92 | [ 22, 'a46ff88886c2ef9762d970b4d2c63678835bd39d' ] 93 | ``` 94 | 95 | ## c32ToB58, b58ToC32 96 | 97 | > **Note**: 98 | > Common address versions are converted between c32check and base58check 99 | > seamlessly, in order to accommodate Stacks addresses. 100 | 101 | ``` 102 | > b58addr = '16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg' 103 | '16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg' 104 | > c32check.b58ToC32(b58addr) 105 | 'SPWNYDJ3STG7XH7ERWXMV6MQ7Q6EATWVY5Q1QMP8' 106 | > c32check.c32ToB58('SPWNYDJ3STG7XH7ERWXMV6MQ7Q6EATWVY5Q1QMP8') 107 | '16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg' 108 | ``` 109 | 110 | ``` 111 | > b58addr = '3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r' 112 | '3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r' 113 | > c32check.b58ToC32(b58addr) 114 | 'SM1Y6EXF21RZ9739DFTEQKB1H044BMM0XVCM4A4NY' 115 | > c32check.c32ToB58('SM1Y6EXF21RZ9739DFTEQKB1H044BMM0XVCM4A4NY') 116 | '3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r' 117 | ``` 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "c32check", 3 | "version": "2.0.0", 4 | "description": "Crockford base-32 checksum encoding", 5 | "main": "lib/index", 6 | "unpkg": "dist/c32check.js", 7 | "jsdelivr": "dist/c32check.js", 8 | "prettier": "@stacks/prettier-config", 9 | "browser": { 10 | "crypto": false 11 | }, 12 | "scripts": { 13 | "webpack": "rimraf lib dist && webpack --mode=production", 14 | "webpack:analyze": "rimraf dist && cross-env NODE_ENV=production ANALYZE=true webpack --mode=production", 15 | "compile": "rimraf lib && tsc", 16 | "prepublishOnly": "npm run build", 17 | "build": "npm run lint && npm run test && npm run webpack && npm run compile", 18 | "lint": "eslint --ext=.ts ./src ./tests", 19 | "prettier": "prettier --write ./src/**/*.ts", 20 | "test": "nyc node ./tests/unitTests/src/index.ts", 21 | "data-set-test": "cross-env BIG_DATA_TESTS=1 nyc node ./tests/unitTests/src/index.ts", 22 | "codecovUpload": "codecov", 23 | "prepare": "husky install .github/husky" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/stacks-network/c32check.git" 28 | }, 29 | "author": { 30 | "name": "Jude Nelson", 31 | "email": "jude@blockstack.com", 32 | "url": "https://blockstack.com" 33 | }, 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/stacks-network/c32check/issues" 37 | }, 38 | "keywords": [ 39 | "blockchain", 40 | "id", 41 | "auth", 42 | "authentication", 43 | "bitcoin", 44 | "blockchain auth", 45 | "blockchain authentication", 46 | "blockchainid", 47 | "blockchain id", 48 | "bitcoin auth", 49 | "bitcoin authentication", 50 | "bitcoin login", 51 | "blockchain login", 52 | "authorization", 53 | "login", 54 | "signin", 55 | "sso", 56 | "crypto", 57 | "cryptography", 58 | "token", 59 | "blockstack", 60 | "blockstack auth", 61 | "profile", 62 | "identity", 63 | "ethereum" 64 | ], 65 | "homepage": "https://stacks.co", 66 | "contributors": [ 67 | { 68 | "name": "Jude Nelson", 69 | "email": "jude@blockstack.com" 70 | }, 71 | { 72 | "name": "Aaron Blankstein", 73 | "email": "aaron@blockstack.com" 74 | }, 75 | { 76 | "name": "Matthew Little", 77 | "email": "matt@blockstack.com" 78 | } 79 | ], 80 | "files": [ 81 | "src", 82 | "lib", 83 | "dist" 84 | ], 85 | "devDependencies": { 86 | "@babel/core": "^7.17.10", 87 | "@babel/preset-env": "^7.17.10", 88 | "@commitlint/cli": "^17.0.3", 89 | "@commitlint/config-conventional": "^17.0.3", 90 | "@stacks/eslint-config": "^1.2.0", 91 | "@stacks/prettier-config": "^0.0.10", 92 | "@types/bs58": "^4.0.1", 93 | "@types/node": "^8.0.0", 94 | "@typescript-eslint/eslint-plugin": "^5.34.0", 95 | "@typescript-eslint/parser": "^5.34.0", 96 | "babel-loader": "^8.2.5", 97 | "codecov": "^3.8.3", 98 | "cross-env": "^7.0.3", 99 | "eslint": "^8.22.0", 100 | "husky": "^8.0.1", 101 | "nyc": "^15.1.0", 102 | "prettier": "^2.7.1", 103 | "rimraf": "^3.0.2", 104 | "source-map-support": "^0.5.19", 105 | "tape": "^5.6.0", 106 | "tape-promise": "^4.0.0", 107 | "ts-loader": "^8.0.2", 108 | "ts-node": "^8.10.2", 109 | "typescript": "^4.6.4", 110 | "webpack": "^5.74.0", 111 | "webpack-bundle-analyzer": "^4.6.1", 112 | "webpack-cli": "^4.10.0" 113 | }, 114 | "dependencies": { 115 | "@noble/hashes": "^1.1.2", 116 | "base-x": "^4.0.0" 117 | }, 118 | "engines": { 119 | "node": ">=8" 120 | }, 121 | "nyc": { 122 | "cache": false, 123 | "all": true, 124 | "extension": [ 125 | ".ts" 126 | ], 127 | "include": [ 128 | "src/**/*.ts" 129 | ], 130 | "exclude": [ 131 | "**/*.d.ts" 132 | ], 133 | "require": [ 134 | "ts-node/register/transpile-only", 135 | "source-map-support/register" 136 | ], 137 | "reporter": [ 138 | "text", 139 | "lcov" 140 | ] 141 | }, 142 | "commitlint": { 143 | "extends": [ 144 | "@commitlint/config-conventional" 145 | ] 146 | }, 147 | "release": { 148 | "branches": "master", 149 | "plugins": [ 150 | "@semantic-release/commit-analyzer", 151 | "@semantic-release/release-notes-generator", 152 | [ 153 | "@semantic-release/exec", 154 | { 155 | "prepareCmd": "npm ci" 156 | } 157 | ], 158 | [ 159 | "@semantic-release/npm", 160 | { 161 | "npmPublish": true 162 | } 163 | ], 164 | [ 165 | "@semantic-release/git", 166 | { 167 | "message": "chore: release ${nextRelease.version}", 168 | "assets": [ 169 | "*.{json,md}" 170 | ] 171 | } 172 | ] 173 | ] 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/address.ts: -------------------------------------------------------------------------------- 1 | import { c32checkEncode, c32checkDecode } from './checksum'; 2 | import * as base58check from './base58check'; 3 | import { bytesToHex } from '@noble/hashes/utils'; 4 | 5 | export const versions = { 6 | mainnet: { 7 | p2pkh: 22, // 'P' 8 | p2sh: 20, // 'M' 9 | }, 10 | testnet: { 11 | p2pkh: 26, // 'T' 12 | p2sh: 21, // 'N' 13 | }, 14 | }; 15 | 16 | // address conversion : bitcoin to stacks 17 | const ADDR_BITCOIN_TO_STACKS: Record = {}; 18 | ADDR_BITCOIN_TO_STACKS[0] = versions.mainnet.p2pkh; 19 | ADDR_BITCOIN_TO_STACKS[5] = versions.mainnet.p2sh; 20 | ADDR_BITCOIN_TO_STACKS[111] = versions.testnet.p2pkh; 21 | ADDR_BITCOIN_TO_STACKS[196] = versions.testnet.p2sh; 22 | 23 | // address conversion : stacks to bitcoin 24 | const ADDR_STACKS_TO_BITCOIN: Record = {}; 25 | ADDR_STACKS_TO_BITCOIN[versions.mainnet.p2pkh] = 0; 26 | ADDR_STACKS_TO_BITCOIN[versions.mainnet.p2sh] = 5; 27 | ADDR_STACKS_TO_BITCOIN[versions.testnet.p2pkh] = 111; 28 | ADDR_STACKS_TO_BITCOIN[versions.testnet.p2sh] = 196; 29 | 30 | /** 31 | * Make a c32check address with the given version and hash160 32 | * The only difference between a c32check string and c32 address 33 | * is that the letter 'S' is pre-pended. 34 | * @param {number} version - the address version number 35 | * @param {string} hash160hex - the hash160 to encode (must be a hash160) 36 | * @returns {string} the address 37 | */ 38 | export function c32address(version: number, hash160hex: string): string { 39 | if (!hash160hex.match(/^[0-9a-fA-F]{40}$/)) { 40 | throw new Error('Invalid argument: not a hash160 hex string'); 41 | } 42 | 43 | const c32string = c32checkEncode(version, hash160hex); 44 | return `S${c32string}`; 45 | } 46 | 47 | /** 48 | * Decode a c32 address into its version and hash160 49 | * @param {string} c32addr - the c32check-encoded address 50 | * @returns {[number, string]} a tuple with the version and hash160 51 | */ 52 | export function c32addressDecode(c32addr: string): [number, string] { 53 | if (c32addr.length <= 5) { 54 | throw new Error('Invalid c32 address: invalid length'); 55 | } 56 | if (c32addr[0] != 'S') { 57 | throw new Error('Invalid c32 address: must start with "S"'); 58 | } 59 | return c32checkDecode(c32addr.slice(1)); 60 | } 61 | 62 | /* 63 | * Convert a base58check address to a c32check address. 64 | * Try to convert the version number if one is not given. 65 | * @param {string} b58check - the base58check encoded address 66 | * @param {number} version - the version number, if not inferred from the address 67 | * @returns {string} the c32 address with the given version number (or the 68 | * semantically-equivalent c32 version number, if not given) 69 | */ 70 | export function b58ToC32(b58check: string, version: number = -1): string { 71 | const addrInfo = base58check.decode(b58check); 72 | const hash160String = bytesToHex(addrInfo.data); 73 | const addrVersion = parseInt(bytesToHex(addrInfo.prefix), 16); 74 | let stacksVersion; 75 | 76 | if (version < 0) { 77 | stacksVersion = addrVersion; 78 | if (ADDR_BITCOIN_TO_STACKS[addrVersion] !== undefined) { 79 | stacksVersion = ADDR_BITCOIN_TO_STACKS[addrVersion]; 80 | } 81 | } else { 82 | stacksVersion = version; 83 | } 84 | 85 | return c32address(stacksVersion, hash160String); 86 | } 87 | 88 | /* 89 | * Convert a c32check address to a base58check address. 90 | * @param {string} c32string - the c32check address 91 | * @param {number} version - the version number, if not inferred from the address 92 | * @returns {string} the base58 address with the given version number (or the 93 | * semantically-equivalent bitcoin version number, if not given) 94 | */ 95 | export function c32ToB58(c32string: string, version: number = -1): string { 96 | const addrInfo = c32addressDecode(c32string); 97 | const stacksVersion = addrInfo[0]; 98 | const hash160String = addrInfo[1]; 99 | let bitcoinVersion; 100 | 101 | if (version < 0) { 102 | bitcoinVersion = stacksVersion; 103 | if (ADDR_STACKS_TO_BITCOIN[stacksVersion] !== undefined) { 104 | bitcoinVersion = ADDR_STACKS_TO_BITCOIN[stacksVersion]; 105 | } 106 | } else { 107 | bitcoinVersion = version; 108 | } 109 | 110 | let prefix = bitcoinVersion.toString(16); 111 | if (prefix.length === 1) { 112 | prefix = `0${prefix}`; 113 | } 114 | 115 | return base58check.encode(hash160String, prefix); 116 | } 117 | -------------------------------------------------------------------------------- /src/base58check.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * From https://github.com/wzbg/base58check 3 | * @Author: zyc 4 | * @Date: 2016-09-11 23:36:05 5 | */ 6 | 'use strict'; 7 | 8 | import { sha256 } from '@noble/hashes/sha256'; 9 | import { hexToBytes } from '@noble/hashes/utils'; 10 | import * as basex from 'base-x'; 11 | 12 | const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; 13 | 14 | export function encode(data: string | Uint8Array, prefix: string | Uint8Array = '00') { 15 | const dataBytes = typeof data === 'string' ? hexToBytes(data) : data; 16 | const prefixBytes = typeof prefix === 'string' ? hexToBytes(prefix) : data; 17 | 18 | if (!(dataBytes instanceof Uint8Array) || !(prefixBytes instanceof Uint8Array)) { 19 | throw new TypeError('Argument must be of type Uint8Array or string'); 20 | } 21 | 22 | const checksum = sha256(sha256(new Uint8Array([...prefixBytes, ...dataBytes]))); 23 | return basex(ALPHABET).encode([...prefixBytes, ...dataBytes, ...checksum.slice(0, 4)]); 24 | } 25 | 26 | export function decode(string: string) { 27 | const bytes = basex(ALPHABET).decode(string); 28 | const prefixBytes = bytes.slice(0, 1); 29 | const dataBytes = bytes.slice(1, -4); 30 | 31 | // todo: for better performance replace spread with `concatBytes` method 32 | const checksum = sha256(sha256(new Uint8Array([...prefixBytes, ...dataBytes]))); 33 | bytes.slice(-4).forEach((check, index) => { 34 | if (check !== checksum[index]) { 35 | throw new Error('Invalid checksum'); 36 | } 37 | }); 38 | return { prefix: prefixBytes, data: dataBytes }; 39 | } 40 | -------------------------------------------------------------------------------- /src/checksum.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha256'; 2 | import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; 3 | import { c32, c32decode, c32encode, c32normalize } from './encoding'; 4 | 5 | /** 6 | * Get the c32check checksum of a hex-encoded string 7 | * @param {string} dataHex - the hex string 8 | * @returns {string} the c32 checksum, as a bin-encoded string 9 | */ 10 | function c32checksum(dataHex: string): string { 11 | const dataHash = sha256(sha256(hexToBytes(dataHex))); 12 | const checksum = bytesToHex(dataHash.slice(0, 4)); 13 | return checksum; 14 | } 15 | 16 | /** 17 | * Encode a hex string as a c32check string. This is a lot like how 18 | * base58check works in Bitcoin-land, but this algorithm uses the 19 | * z-base-32 alphabet instead of the base58 alphabet. The algorithm 20 | * is as follows: 21 | * * calculate the c32checksum of version + data 22 | * * c32encode version + data + c32checksum 23 | * @param {number} version - the version string (between 0 and 31) 24 | * @param {string} data - the data to encode 25 | * @returns {string} the c32check representation 26 | */ 27 | export function c32checkEncode(version: number, data: string): string { 28 | if (version < 0 || version >= 32) { 29 | throw new Error('Invalid version (must be between 0 and 31)'); 30 | } 31 | if (!data.match(/^[0-9a-fA-F]*$/)) { 32 | throw new Error('Invalid data (not a hex string)'); 33 | } 34 | 35 | data = data.toLowerCase(); 36 | if (data.length % 2 !== 0) { 37 | data = `0${data}`; 38 | } 39 | 40 | let versionHex = version.toString(16); 41 | if (versionHex.length === 1) { 42 | versionHex = `0${versionHex}`; 43 | } 44 | 45 | const checksumHex = c32checksum(`${versionHex}${data}`); 46 | const c32str = c32encode(`${data}${checksumHex}`); 47 | return `${c32[version]}${c32str}`; 48 | } 49 | 50 | /* 51 | * Decode a c32check string back into its version and data payload. This is 52 | * a lot like how base58check works in Bitcoin-land, but this algorithm uses 53 | * the z-base-32 alphabet instead of the base58 alphabet. The algorithm 54 | * is as follows: 55 | * * extract the version, data, and checksum 56 | * * verify the checksum matches c32checksum(version + data) 57 | * * return data 58 | * @param {string} c32data - the c32check-encoded string 59 | * @returns {array} [version (number), data (string)]. The returned data 60 | * will be a hex string. Throws an exception if the checksum does not match. 61 | */ 62 | export function c32checkDecode(c32data: string): [number, string] { 63 | c32data = c32normalize(c32data); 64 | const dataHex = c32decode(c32data.slice(1)); 65 | const versionChar = c32data[0]; 66 | const version = c32.indexOf(versionChar); 67 | const checksum = dataHex.slice(-8); 68 | 69 | let versionHex = version.toString(16); 70 | if (versionHex.length === 1) { 71 | versionHex = `0${versionHex}`; 72 | } 73 | 74 | if (c32checksum(`${versionHex}${dataHex.substring(0, dataHex.length - 8)}`) !== checksum) { 75 | throw new Error('Invalid c32check string: checksum mismatch'); 76 | } 77 | 78 | return [version, dataHex.substring(0, dataHex.length - 8)]; 79 | } 80 | -------------------------------------------------------------------------------- /src/encoding.ts: -------------------------------------------------------------------------------- 1 | import { hexToBytes } from '@noble/hashes/utils'; 2 | 3 | export const c32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; 4 | const hex = '0123456789abcdef'; 5 | 6 | /** 7 | * Encode a hex string as a c32 string. Note that the hex string is assumed 8 | * to be big-endian (and the resulting c32 string will be as well). 9 | * @param {string} inputHex - the input to encode 10 | * @param {number} minLength - the minimum length of the c32 string 11 | * @returns {string} the c32check-encoded representation of the data, as a string 12 | */ 13 | export function c32encode(inputHex: string, minLength?: number): string { 14 | // must be hex 15 | if (!inputHex.match(/^[0-9a-fA-F]*$/)) { 16 | throw new Error('Not a hex-encoded string'); 17 | } 18 | 19 | if (inputHex.length % 2 !== 0) { 20 | inputHex = `0${inputHex}`; 21 | } 22 | 23 | inputHex = inputHex.toLowerCase(); 24 | 25 | let res = []; 26 | let carry = 0; 27 | for (let i = inputHex.length - 1; i >= 0; i--) { 28 | if (carry < 4) { 29 | const currentCode = hex.indexOf(inputHex[i]) >> carry; 30 | let nextCode = 0; 31 | if (i !== 0) { 32 | nextCode = hex.indexOf(inputHex[i - 1]); 33 | } 34 | // carry = 0, nextBits is 1, carry = 1, nextBits is 2 35 | const nextBits = 1 + carry; 36 | const nextLowBits = nextCode % (1 << nextBits) << (5 - nextBits); 37 | const curC32Digit = c32[currentCode + nextLowBits]; 38 | carry = nextBits; 39 | res.unshift(curC32Digit); 40 | } else { 41 | carry = 0; 42 | } 43 | } 44 | 45 | let C32leadingZeros = 0; 46 | for (let i = 0; i < res.length; i++) { 47 | if (res[i] !== '0') { 48 | break; 49 | } else { 50 | C32leadingZeros++; 51 | } 52 | } 53 | 54 | res = res.slice(C32leadingZeros); 55 | 56 | const zeroPrefix = new TextDecoder().decode(hexToBytes(inputHex)).match(/^\u0000*/); 57 | const numLeadingZeroBytesInHex = zeroPrefix ? zeroPrefix[0].length : 0; 58 | 59 | for (let i = 0; i < numLeadingZeroBytesInHex; i++) { 60 | res.unshift(c32[0]); 61 | } 62 | 63 | if (minLength) { 64 | const count = minLength - res.length; 65 | for (let i = 0; i < count; i++) { 66 | res.unshift(c32[0]); 67 | } 68 | } 69 | 70 | return res.join(''); 71 | } 72 | 73 | /* 74 | * Normalize a c32 string 75 | * @param {string} c32input - the c32-encoded input string 76 | * @returns {string} the canonical representation of the c32 input string 77 | */ 78 | export function c32normalize(c32input: string): string { 79 | // must be upper-case 80 | // replace all O's with 0's 81 | // replace all I's and L's with 1's 82 | return c32input.toUpperCase().replace(/O/g, '0').replace(/L|I/g, '1'); 83 | } 84 | 85 | /* 86 | * Decode a c32 string back into a hex string. Note that the c32 input 87 | * string is assumed to be big-endian (and the resulting hex string will 88 | * be as well). 89 | * @param {string} c32input - the c32-encoded input to decode 90 | * @param {number} minLength - the minimum length of the output hex string (in bytes) 91 | * @returns {string} the hex-encoded representation of the data, as a string 92 | */ 93 | export function c32decode(c32input: string, minLength?: number): string { 94 | c32input = c32normalize(c32input); 95 | 96 | // must result in a c32 string 97 | if (!c32input.match(`^[${c32}]*$`)) { 98 | throw new Error('Not a c32-encoded string'); 99 | } 100 | 101 | const zeroPrefix = c32input.match(`^${c32[0]}*`); 102 | const numLeadingZeroBytes = zeroPrefix ? zeroPrefix[0].length : 0; 103 | 104 | let res = []; 105 | let carry = 0; 106 | let carryBits = 0; 107 | for (let i = c32input.length - 1; i >= 0; i--) { 108 | if (carryBits === 4) { 109 | res.unshift(hex[carry]); 110 | carryBits = 0; 111 | carry = 0; 112 | } 113 | const currentCode = c32.indexOf(c32input[i]) << carryBits; 114 | const currentValue = currentCode + carry; 115 | const currentHexDigit = hex[currentValue % 16]; 116 | carryBits += 1; 117 | carry = currentValue >> 4; 118 | if (carry > 1 << carryBits) { 119 | throw new Error('Panic error in decoding.'); 120 | } 121 | res.unshift(currentHexDigit); 122 | } 123 | // one last carry 124 | res.unshift(hex[carry]); 125 | 126 | if (res.length % 2 === 1) { 127 | res.unshift('0'); 128 | } 129 | 130 | let hexLeadingZeros = 0; 131 | for (let i = 0; i < res.length; i++) { 132 | if (res[i] !== '0') { 133 | break; 134 | } else { 135 | hexLeadingZeros++; 136 | } 137 | } 138 | 139 | res = res.slice(hexLeadingZeros - (hexLeadingZeros % 2)); 140 | 141 | let hexStr = res.join(''); 142 | for (let i = 0; i < numLeadingZeroBytes; i++) { 143 | hexStr = `00${hexStr}`; 144 | } 145 | 146 | if (minLength) { 147 | const count = minLength * 2 - hexStr.length; 148 | for (let i = 0; i < count; i += 2) { 149 | hexStr = `00${hexStr}`; 150 | } 151 | } 152 | 153 | return hexStr; 154 | } 155 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { c32encode, c32decode, c32normalize } from './encoding'; 2 | 3 | import { c32checkEncode, c32checkDecode } from './checksum'; 4 | 5 | import { c32address, c32addressDecode, c32ToB58, b58ToC32, versions } from './address'; 6 | 7 | export { 8 | c32encode, 9 | c32decode, 10 | c32checkEncode, 11 | c32checkDecode, 12 | c32address, 13 | c32addressDecode, 14 | c32normalize, 15 | versions, 16 | c32ToB58, 17 | b58ToC32, 18 | }; 19 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": [ 7 | "./unitTests/src/**/*", 8 | "../src/**/*", 9 | ] 10 | } -------------------------------------------------------------------------------- /tests/unitTests/src/index.ts: -------------------------------------------------------------------------------- 1 | import test = require('tape-promise/tape'); 2 | import * as process from 'process'; 3 | import { 4 | c32encode, 5 | c32decode, 6 | c32checkEncode, 7 | c32checkDecode, 8 | c32address, 9 | c32addressDecode, 10 | c32ToB58, 11 | b58ToC32, 12 | } from '../../../src/index'; 13 | import { encode } from '../../../src/base58check'; 14 | import * as c32check from '../../../src/index'; 15 | 16 | export function c32encodingTests() { 17 | const hexStrings = [ 18 | 'a46ff88886c2ef9762d970b4d2c63678835bd39d', 19 | '', 20 | '0000000000000000000000000000000000000000', 21 | '0000000000000000000000000000000000000001', 22 | '1000000000000000000000000000000000000001', 23 | '1000000000000000000000000000000000000000', 24 | '1', 25 | '22', 26 | '001', 27 | '0001', 28 | '00001', 29 | '000001', 30 | '0000001', 31 | '00000001', 32 | '10', 33 | '100', 34 | '1000', 35 | '10000', 36 | '100000', 37 | '1000000', 38 | '10000000', 39 | '100000000', 40 | ]; 41 | 42 | const c32minLengths = [ 43 | undefined, 44 | undefined, 45 | 20, 46 | 20, 47 | 32, 48 | 32, 49 | undefined, 50 | undefined, 51 | undefined, 52 | undefined, 53 | undefined, 54 | undefined, 55 | undefined, 56 | undefined, 57 | undefined, 58 | undefined, 59 | undefined, 60 | undefined, 61 | undefined, 62 | undefined, 63 | undefined, 64 | undefined, 65 | ]; 66 | 67 | const c32Strings = [ 68 | 'MHQZH246RBQSERPSE2TD5HHPF21NQMWX', 69 | '', 70 | '00000000000000000000', 71 | '00000000000000000001', 72 | '20000000000000000000000000000001', 73 | '20000000000000000000000000000000', 74 | '1', 75 | '12', 76 | '01', 77 | '01', 78 | '001', 79 | '001', 80 | '0001', 81 | '0001', 82 | 'G', 83 | '80', 84 | '400', 85 | '2000', 86 | '10000', 87 | 'G0000', 88 | '800000', 89 | '4000000', 90 | ]; 91 | 92 | const hexMinLengths = [ 93 | undefined, 94 | undefined, 95 | 20, 96 | 20, 97 | 20, 98 | 20, 99 | undefined, 100 | undefined, 101 | undefined, 102 | undefined, 103 | undefined, 104 | undefined, 105 | undefined, 106 | undefined, 107 | undefined, 108 | undefined, 109 | undefined, 110 | undefined, 111 | undefined, 112 | undefined, 113 | undefined, 114 | undefined, 115 | undefined, 116 | ]; 117 | 118 | test('c32encode', t => { 119 | t.plan(hexStrings.length * 3); 120 | for (let i = 0; i < hexStrings.length; i++) { 121 | const z = c32encode(hexStrings[i].toLowerCase(), c32minLengths[i]); 122 | t.equal(z, c32Strings[i], 'c32encode: ' + `expected ${c32Strings[i]}, got ${z}`); 123 | 124 | const zPadded = c32encode(hexStrings[i].toLowerCase(), z.length + 5); 125 | t.equal( 126 | zPadded, 127 | `00000${c32Strings[i]}`, 128 | 'c32encode padded: ' + `expected 00000${c32Strings[i]}, got ${zPadded}` 129 | ); 130 | 131 | const zNoLength = c32encode(hexStrings[i].toUpperCase()); 132 | t.equal( 133 | zNoLength, 134 | c32Strings[i], 135 | 'c32encode length deduced: ' + `expected ${c32Strings[i]}, got ${zNoLength}` 136 | ); 137 | } 138 | }); 139 | 140 | test('c32decode', t => { 141 | t.plan(c32Strings.length * 3); 142 | for (let i = 0; i < c32Strings.length; i++) { 143 | const h = c32decode(c32Strings[i], hexMinLengths[i]); 144 | const paddedHexString = hexStrings[i].length % 2 === 0 ? hexStrings[i] : `0${hexStrings[i]}`; 145 | t.equal(h, paddedHexString, 'c32decode: ' + `expected ${paddedHexString}, got ${h}`); 146 | 147 | const hPadded = c32decode(c32Strings[i], h.length / 2 + 5); 148 | t.equal( 149 | hPadded, 150 | `0000000000${paddedHexString}`, 151 | 'c32decode padded: ' + `expected ${paddedHexString}, got ${hPadded}` 152 | ); 153 | 154 | const hNoLength = c32decode(c32Strings[i]); 155 | t.equal( 156 | hNoLength, 157 | paddedHexString, 158 | 'c32decode length deduced: ' + `expected ${paddedHexString}, got ${hNoLength}` 159 | ); 160 | } 161 | }); 162 | 163 | test('invalid input', t => { 164 | t.plan(2); 165 | try { 166 | c32encode('abcdefg'); 167 | t.ok(false); 168 | } catch (e) { 169 | t.ok(true, 'invalid hex'); 170 | } 171 | try { 172 | c32decode('wtu'); 173 | t.ok(false); 174 | } catch (e) { 175 | t.ok(true, 'invalid c32'); 176 | } 177 | }); 178 | } 179 | 180 | export function c32encodingRandomBytes() { 181 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires 182 | const testData: { hex: string; c32: string }[] = require('../data/random.json'); 183 | 184 | test('c32encode', t => { 185 | t.plan(testData.length); 186 | testData.map(testData => { 187 | const actualC32 = c32encode(testData.hex, testData.c32.length); 188 | const expectedC32 = testData.c32; 189 | if (actualC32.length === expectedC32.length + 1) { 190 | t.equal(actualC32, `0${expectedC32}`, 'Should match test data from external library.'); 191 | } else { 192 | t.equal(actualC32, expectedC32, 'Should match test data from external library.'); 193 | } 194 | }); 195 | }); 196 | 197 | test('c32decode', t => { 198 | t.plan(testData.length); 199 | testData.map(testData => { 200 | const actualHex = c32decode(testData.c32, testData.hex.length / 2); 201 | const expectedHex = testData.hex; 202 | t.equal(actualHex, expectedHex, 'Should match test hex data from external library.'); 203 | if (actualHex !== expectedHex) { 204 | throw new Error('FAILING FAST HERE'); 205 | } 206 | }); 207 | }); 208 | } 209 | 210 | export function c32checkEncodingTests() { 211 | const hexStrings = [ 212 | 'a46ff88886c2ef9762d970b4d2c63678835bd39d', 213 | '', 214 | '0000000000000000000000000000000000000000', 215 | '0000000000000000000000000000000000000001', 216 | '1000000000000000000000000000000000000001', 217 | '1000000000000000000000000000000000000000', 218 | '1', 219 | '22', 220 | '001', 221 | '0001', 222 | '00001', 223 | '000001', 224 | '0000001', 225 | '00000001', 226 | '10', 227 | '100', 228 | '1000', 229 | '10000', 230 | '100000', 231 | '1000000', 232 | '10000000', 233 | '100000000', 234 | ]; 235 | 236 | const versions = [22, 0, 31, 11, 17, 2]; 237 | 238 | const c32strings = [ 239 | [ 240 | 'P2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7', 241 | 'P37JJX3D', 242 | 'P000000000000000000002Q6VF78', 243 | 'P00000000000000000005JA84HQ', 244 | 'P80000000000000000000000000000004R0CMNV', 245 | 'P800000000000000000000000000000033H8YKK', 246 | 'P4VKEFGY', 247 | 'P4ABAT49T', 248 | 'P040SMAT7', 249 | 'P040SMAT7', 250 | 'P007S3BZWD', 251 | 'P007S3BZWD', 252 | 'P0005MDH0A2', 253 | 'P0005MDH0A2', 254 | 'P22J7S4CS', 255 | 'P101ST5JKW', 256 | 'PG02NDNFP7', 257 | 'P80022RTP9J', 258 | 'P40002HQ7B52', 259 | 'P200003AWNGGR', 260 | 'P1000003BCJ108', 261 | 'PG000000DMPNB9', 262 | ], 263 | [ 264 | '02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE', 265 | '0A0DR2R', 266 | '0000000000000000000002AA028H', 267 | '000000000000000000006EKBDDS', 268 | '080000000000000000000000000000007R1QC00', 269 | '080000000000000000000000000000003ENTGCQ', 270 | '04C407K6', 271 | '049Q1W6AP', 272 | '006NZP224', 273 | '006NZP224', 274 | '0007YBH12H', 275 | '0007YBH12H', 276 | '000053HGS6K', 277 | '000053HGS6K', 278 | '021732WNV', 279 | '0103H9VB3W', 280 | '0G02BDQDTZ', 281 | '08002CT6SBA', 282 | '0400012P9QQ9', 283 | '02000008BW4AV', 284 | '010000013625RF', 285 | '0G000001QFSPXM', 286 | ], 287 | [ 288 | 'Z2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR', 289 | 'Z44N8Q4', 290 | 'Z000000000000000000002ZE1VMN', 291 | 'Z00000000000000000005HZ3DVN', 292 | 'Z80000000000000000000000000000004XBV6MS', 293 | 'Z800000000000000000000000000000007VF5G0', 294 | 'Z6RHFJAJ', 295 | 'Z4BM8HYJA', 296 | 'Z05NKF50D', 297 | 'Z05NKF50D', 298 | 'Z004720442', 299 | 'Z004720442', 300 | 'Z00073C2AR7', 301 | 'Z00073C2AR7', 302 | 'Z23M13WT9', 303 | 'Z103F8N2SE', 304 | 'ZG02G54C7T', 305 | 'Z8000MKD341', 306 | 'Z40003HGBBVV', 307 | 'Z2000039BDD6F', 308 | 'Z100000082GT4Q', 309 | 'ZG0000021P09KP', 310 | ], 311 | [ 312 | 'B2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNGTQ5XV', 313 | 'B29AKKQ8', 314 | 'B000000000000000000001A6KF5R', 315 | 'B00000000000000000004TNHE36', 316 | 'B80000000000000000000000000000007N1Y0J3', 317 | 'B80000000000000000000000000000001P0H0EC', 318 | 'B40R2K2V', 319 | 'B4BCDY460', 320 | 'B04PB501R', 321 | 'B04PB501R', 322 | 'B0057NK813', 323 | 'B0057NK813', 324 | 'B00048S8YNY', 325 | 'B00048S8YNY', 326 | 'B20QX4FW0', 327 | 'B102PC6RCC', 328 | 'BG02G1QXCQ', 329 | 'B8000FWS04R', 330 | 'B40001KAMP9Y', 331 | 'B200002DNYYYC', 332 | 'B1000003P9CPW6', 333 | 'BG000003473Z3W', 334 | ], 335 | [ 336 | 'H2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPZJKGHG', 337 | 'HXQCX36', 338 | 'H00000000000000000000ZKV5K0', 339 | 'H000000000000000000049FQ4N0', 340 | 'H800000000000000000000000000000043X9S3R', 341 | 'H80000000000000000000000000000002R04Y9K', 342 | 'H4NDX0WY', 343 | 'H48VZCZQ1', 344 | 'H05JF5G0A', 345 | 'H05JF5G0A', 346 | 'H007KAN0NP', 347 | 'H007KAN0NP', 348 | 'H000663B0ZQ', 349 | 'H000663B0ZQ', 350 | 'H23SE241P', 351 | 'H102X2YQF6', 352 | 'HG0322PNKV', 353 | 'H8000JDRJP4', 354 | 'H40003YJA8JD', 355 | 'H200001ZTRYYH', 356 | 'H1000002QFX7E6', 357 | 'HG000000PPMVDM', 358 | ], 359 | [ 360 | '22J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKMQMB2T9', 361 | '2EC7BFA', 362 | '2000000000000000000003BMZJ0A', 363 | '200000000000000000004CF2C9N', 364 | '280000000000000000000000000000005Z78VV5', 365 | '280000000000000000000000000000000SJ03P9', 366 | '24N9YTH0', 367 | '24ATP2H2P', 368 | '206CXSP43', 369 | '206CXSP43', 370 | '2006CWFQ58', 371 | '2006CWFQ58', 372 | '20007TGK2A5', 373 | '20007TGK2A5', 374 | '222Q3MF1Q', 375 | '2100EZ96RY', 376 | '2G01YNNNTE', 377 | '28001HQ43QG', 378 | '240002P4722F', 379 | '2200001ASE5V7', 380 | '210000038X74ER', 381 | '2G000003FNKA3P', 382 | ], 383 | ]; 384 | 385 | test('c32checkEncode', t => { 386 | t.plan(hexStrings.length * versions.length * (3 + 14)); 387 | for (let i = 0; i < hexStrings.length; i++) { 388 | for (let j = 0; j < versions.length; j++) { 389 | const h = hexStrings[i]; 390 | const v = versions[j]; 391 | const z = c32checkEncode(v, h); 392 | t.equal( 393 | c32strings[j][i], 394 | z, 395 | `c32checkEncode version=${v} ${h}: ` + `expect ${c32strings[j][i]}, got ${z}` 396 | ); 397 | 398 | const decoded = c32checkDecode(z); 399 | const decodedVersion = decoded[0]; 400 | const decodedHex = decoded[1]; 401 | const paddedExpectedHex = h.length % 2 !== 0 ? `0${h}` : h; 402 | 403 | t.equal(decodedVersion, v, `c32checkDecode ${z}: expect ver ${v}, got ${decodedVersion}`); 404 | t.equal( 405 | decodedHex, 406 | paddedExpectedHex, 407 | `c32decode ${z}: expect hex ${paddedExpectedHex}, got ${decodedHex}` 408 | ); 409 | 410 | const withI = z.replace(/1/g, 'I'); 411 | const withi = z.replace(/1/g, 'i'); 412 | const withL = z.replace(/1/g, 'L'); 413 | const withl = z.replace(/1/g, 'l'); 414 | const withO = z.replace(/0/g, 'O'); 415 | const witho = z.replace(/0/g, 'o'); 416 | const lowerCase = z.toLowerCase(); 417 | 418 | const homoglyphs = [withI, withi, withL, withl, withO, witho, lowerCase]; 419 | 420 | for (let k = 0; k < homoglyphs.length; k++) { 421 | const decodedHomoglyph = c32checkDecode(homoglyphs[k]); 422 | const decodedHomoglyphVersion = decodedHomoglyph[0]; 423 | const decodedHomoglyphHex = decodedHomoglyph[1]; 424 | const paddedExpectedHomoglyphHex = h.length % 2 !== 0 ? `0${h}` : h; 425 | 426 | t.equal( 427 | decodedHomoglyphVersion, 428 | v, 429 | `c32checkDecode homoglyph ${homoglyphs[k]}: ` + 430 | `expect ${v}, got ${decodedHomoglyphVersion}` 431 | ); 432 | 433 | t.equal( 434 | decodedHomoglyphHex, 435 | paddedExpectedHomoglyphHex, 436 | `c32checkDecode homoglyph ${homoglyphs[k]}: ` + 437 | `expect ${paddedExpectedHomoglyphHex}, got ${decodedHomoglyphHex}` 438 | ); 439 | } 440 | } 441 | } 442 | }); 443 | 444 | test('c32checkEncode invalid inputs', t => { 445 | t.plan(4); 446 | try { 447 | c32checkEncode(22, 'abcdefg'); 448 | t.ok(false); 449 | } catch (e) { 450 | t.ok(true, 'invalid hex'); 451 | } 452 | try { 453 | c32checkDecode('Wtz'); 454 | t.ok(false); 455 | } catch (e) { 456 | t.ok(true, 'invalid c32'); 457 | } 458 | try { 459 | c32checkDecode('sn1g96reo5bq9f5n5famjwsgg3hegs6uuia5jq19'); 460 | c32checkDecode('sn1g96reo5bq9f5n5famjwsgg3hegs6uuia5jq1'); 461 | c32checkDecode('sia5jq18'); 462 | t.ok(false); 463 | } catch (e) { 464 | t.ok(true, 'invalid checksum'); 465 | } 466 | try { 467 | c32checkEncode(32, 'abcdef'); 468 | c32checkEncode(-1, 'abcdef'); 469 | c32checkEncode(100, 'abcdef'); 470 | t.ok(false); 471 | } catch (e) { 472 | t.ok(true, 'invalid version'); 473 | } 474 | }); 475 | } 476 | 477 | export function c32addressTests() { 478 | const hexStrings = [ 479 | 'a46ff88886c2ef9762d970b4d2c63678835bd39d', 480 | '0000000000000000000000000000000000000000', 481 | '0000000000000000000000000000000000000001', 482 | '1000000000000000000000000000000000000001', 483 | '1000000000000000000000000000000000000000', 484 | ]; 485 | 486 | const versions = [22, 0, 31, 20, 26, 21]; 487 | 488 | const c32addresses = [ 489 | [ 490 | 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7', 491 | 'SP000000000000000000002Q6VF78', 492 | 'SP00000000000000000005JA84HQ', 493 | 'SP80000000000000000000000000000004R0CMNV', 494 | 'SP800000000000000000000000000000033H8YKK', 495 | ], 496 | [ 497 | 'S02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE', 498 | 'S0000000000000000000002AA028H', 499 | 'S000000000000000000006EKBDDS', 500 | 'S080000000000000000000000000000007R1QC00', 501 | 'S080000000000000000000000000000003ENTGCQ', 502 | ], 503 | [ 504 | 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR', 505 | 'SZ000000000000000000002ZE1VMN', 506 | 'SZ00000000000000000005HZ3DVN', 507 | 'SZ80000000000000000000000000000004XBV6MS', 508 | 'SZ800000000000000000000000000000007VF5G0', 509 | ], 510 | [ 511 | 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G', 512 | 'SM0000000000000000000062QV6X', 513 | 'SM00000000000000000005VR75B2', 514 | 'SM80000000000000000000000000000004WBEWKC', 515 | 'SM80000000000000000000000000000000JGSYGV', 516 | ], 517 | [ 518 | 'ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ', 519 | 'ST000000000000000000002AMW42H', 520 | 'ST000000000000000000042DB08Y', 521 | 'ST80000000000000000000000000000006BYJ4R4', 522 | 'ST80000000000000000000000000000002YBNPV3', 523 | ], 524 | [ 525 | 'SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKP6D2ZK9', 526 | 'SN000000000000000000003YDHWKJ', 527 | 'SN00000000000000000005341MC8', 528 | 'SN800000000000000000000000000000066KZWY0', 529 | 'SN800000000000000000000000000000006H75AK', 530 | ], 531 | ]; 532 | 533 | test('c32address', t => { 534 | t.plan(hexStrings.length * versions.length * 3); 535 | for (let i = 0; i < hexStrings.length; i++) { 536 | for (let j = 0; j < versions.length; j++) { 537 | const h = hexStrings[i]; 538 | const v = versions[j]; 539 | const z = c32address(v, h); 540 | t.equal( 541 | c32addresses[j][i], 542 | z, 543 | `c32address version=${v} ${h}: ` + `expect ${c32addresses[j][i]}, got ${z}` 544 | ); 545 | 546 | const decoded = c32addressDecode(z); 547 | const decodedVersion = decoded[0]; 548 | const decodedHex = decoded[1]; 549 | const paddedExpectedHex = h.length % 2 !== 0 ? `0${h}` : h; 550 | 551 | t.equal(decodedVersion, v, `c32addressDecode ${z}: expect ver ${v}, got ${decodedVersion}`); 552 | t.equal( 553 | decodedHex, 554 | paddedExpectedHex, 555 | `c32addressDecode ${z}: expect hex ${paddedExpectedHex}, got ${decodedHex}` 556 | ); 557 | } 558 | } 559 | }); 560 | 561 | test('c32address invalid input', t => { 562 | const invalids = [ 563 | () => c32address(-1, 'a46ff88886c2ef9762d970b4d2c63678835bd39d'), 564 | () => c32address(32, 'a46ff88886c2ef9762d970b4d2c63678835bd39d'), 565 | () => c32address(5, 'a46ff88886c2ef9762d970b4d2c63678835bd39d00'), 566 | () => c32address(5, 'a46ff88886c2ef9762d970b4d2c63678835bd3'), 567 | () => c32address(5, 'a46ff88886c2ef9762d970b4d2c63678835bd39d0'), 568 | () => c32address(5, 'a46ff88886c2ef9762d970b4d2c63678835bd39'), 569 | ]; 570 | 571 | const invalidDecodes = [ 572 | () => c32addressDecode('ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ0'), 573 | () => c32addressDecode('ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RR'), 574 | () => c32addressDecode('ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0R'), 575 | () => c32addressDecode('ST2J'), 576 | () => c32addressDecode('bP2CT665Q0JB7P39TZ7BST0QYCAQSMJWBZK8QT35J'), 577 | ]; 578 | 579 | t.plan(invalids.length + invalidDecodes.length); 580 | for (let i = 0; i < invalids.length; i++) { 581 | try { 582 | invalids[i](); 583 | t.ok(false, 'parsed invalid input'); 584 | } catch (e) { 585 | t.ok(true, `invalid input case ${i}`); 586 | } 587 | } 588 | 589 | for (let i = 0; i < invalidDecodes.length; i++) { 590 | try { 591 | invalidDecodes[i](); 592 | t.ok(false, 'decoded invalid address'); 593 | } catch (e) { 594 | t.ok(true, `invalid address decode case ${i}`); 595 | } 596 | } 597 | }); 598 | } 599 | 600 | export function c32ToB58Test() { 601 | const hexStrings = [ 602 | 'a46ff88886c2ef9762d970b4d2c63678835bd39d', 603 | '0000000000000000000000000000000000000000', 604 | '0000000000000000000000000000000000000001', 605 | '1000000000000000000000000000000000000001', 606 | '1000000000000000000000000000000000000000', 607 | ]; 608 | 609 | const versions = [22, 0, 31, 20, 26, 21]; 610 | 611 | const c32addresses = [ 612 | [ 613 | 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7', 614 | 'SP000000000000000000002Q6VF78', 615 | 'SP00000000000000000005JA84HQ', 616 | 'SP80000000000000000000000000000004R0CMNV', 617 | 'SP800000000000000000000000000000033H8YKK', 618 | ], 619 | [ 620 | 'S02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE', 621 | 'S0000000000000000000002AA028H', 622 | 'S000000000000000000006EKBDDS', 623 | 'S080000000000000000000000000000007R1QC00', 624 | 'S080000000000000000000000000000003ENTGCQ', 625 | ], 626 | [ 627 | 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR', 628 | 'SZ000000000000000000002ZE1VMN', 629 | 'SZ00000000000000000005HZ3DVN', 630 | 'SZ80000000000000000000000000000004XBV6MS', 631 | 'SZ800000000000000000000000000000007VF5G0', 632 | ], 633 | [ 634 | 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G', 635 | 'SM0000000000000000000062QV6X', 636 | 'SM00000000000000000005VR75B2', 637 | 'SM80000000000000000000000000000004WBEWKC', 638 | 'SM80000000000000000000000000000000JGSYGV', 639 | ], 640 | [ 641 | 'ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ', 642 | 'ST000000000000000000002AMW42H', 643 | 'ST000000000000000000042DB08Y', 644 | 'ST80000000000000000000000000000006BYJ4R4', 645 | 'ST80000000000000000000000000000002YBNPV3', 646 | ], 647 | [ 648 | 'SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKP6D2ZK9', 649 | 'SN000000000000000000003YDHWKJ', 650 | 'SN00000000000000000005341MC8', 651 | 'SN800000000000000000000000000000066KZWY0', 652 | 'SN800000000000000000000000000000006H75AK', 653 | ], 654 | ]; 655 | 656 | const b58addresses = [ 657 | [ 658 | 'A7RjcihhakxJfAqgwTVsLTyc8kbhDJPMVY', 659 | '9rSGfPZLcyCGzY4uYEL1fkzJr6fkicS2rs', 660 | '9rSGfPZLcyCGzY4uYEL1fkzJr6fkoGa2eS', 661 | '9stsUTaRHnyTRFWnbwiyCWwfpkkKCFYBD4', 662 | '9stsUTaRHnyTRFWnbwiyCWwfpkkK9ZxEPC', 663 | ], 664 | [ 665 | '1FzTxL9Mxnm2fdmnQEArfhzJHevwbvcH6d', 666 | '1111111111111111111114oLvT2', 667 | '11111111111111111111BZbvjr', 668 | '12Tbp525fpnBRiSt4iPxXkxMyf5Ze1UeZu', 669 | '12Tbp525fpnBRiSt4iPxXkxMyf5ZWzA5TC', 670 | ], 671 | [ 672 | 'DjUAUhPHyP8C256UAEVjhbRgoHvBetzPRR', 673 | 'DUUhXNEw1bNAMSKgm1Kt2tSPWdzF8952Np', 674 | 'DUUhXNEw1bNAMSKgm1Kt2tSPWdzFCMncsE', 675 | 'DVwJLSG1gR9Ln9mZpiiqZePkVJ4obdg7UC', 676 | 'DVwJLSG1gR9Ln9mZpiiqZePkVJ4oTzMnyD', 677 | ], 678 | [ 679 | '9JkXeW78AQ2Z2JZWtcqENDS2sk5orG4ggw', 680 | '93m4hAxmCcGXMfnjVPfNhWSjb69sDziGSY', 681 | '93m4hAxmCcGXMfnjVPfNhWSjb69sPHPDTX', 682 | '95DfWEyqsS3hnPEcZ74LEGQ6ZkERn1FuUo', 683 | '95DfWEyqsS3hnPEcZ74LEGQ6ZkERexa3xe', 684 | ], 685 | [ 686 | 'Bin9Z9trRUoovuQ338q9Gy4kemdU7ni2FG', 687 | 'BTngbpkVTh3nGGdFdufHcG5TN7hXYuX31z', 688 | 'BTngbpkVTh3nGGdFdufHcG5TN7hXbks9tq', 689 | 'BVFHQtma8Wpxgz58hd4F922pLmn65qtPy5', 690 | 'BVFHQtma8Wpxgz58hd4F922pLmn5zEwasC', 691 | ], 692 | [ 693 | '9i68dcQQsaVRqjhbv3AYrLhpWFLkWkzrCG', 694 | '9T6fgHG3unjQB6vpWozhBdiXDbQp3P7F8M', 695 | '9T6fgHG3unjQB6vpWozhBdiXDbQp5FwEH5', 696 | '9UZGVMH8acWabpNhaXPeiPftCFVNXQAYoZ', 697 | '9UZGVMH8acWabpNhaXPeiPftCFVNMacQDQ', 698 | ], 699 | ]; 700 | 701 | const equivalentVersions = [22, 20, 26, 21]; 702 | 703 | const c32addressesEquivalentVersion = [ 704 | [ 705 | 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7', 706 | 'SP000000000000000000002Q6VF78', 707 | 'SP00000000000000000005JA84HQ', 708 | 'SP80000000000000000000000000000004R0CMNV', 709 | 'SP800000000000000000000000000000033H8YKK', 710 | ], 711 | [ 712 | 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G', 713 | 'SM0000000000000000000062QV6X', 714 | 'SM00000000000000000005VR75B2', 715 | 'SM80000000000000000000000000000004WBEWKC', 716 | 'SM80000000000000000000000000000000JGSYGV', 717 | ], 718 | [ 719 | 'ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ', 720 | 'ST000000000000000000002AMW42H', 721 | 'ST000000000000000000042DB08Y', 722 | 'ST80000000000000000000000000000006BYJ4R4', 723 | 'ST80000000000000000000000000000002YBNPV3', 724 | ], 725 | [ 726 | 'SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKP6D2ZK9', 727 | 'SN000000000000000000003YDHWKJ', 728 | 'SN00000000000000000005341MC8', 729 | 'SN800000000000000000000000000000066KZWY0', 730 | 'SN800000000000000000000000000000006H75AK', 731 | ], 732 | ]; 733 | 734 | const b58addressesEquivalentVersion = [ 735 | [ 736 | '1FzTxL9Mxnm2fdmnQEArfhzJHevwbvcH6d', 737 | '1111111111111111111114oLvT2', 738 | '11111111111111111111BZbvjr', 739 | '12Tbp525fpnBRiSt4iPxXkxMyf5Ze1UeZu', 740 | '12Tbp525fpnBRiSt4iPxXkxMyf5ZWzA5TC', 741 | ], 742 | [ 743 | '3GgUssdoWh5QkoUDXKqT6LMESBDf8aqp2y', 744 | '31h1vYVSYuKP6AhS86fbRdMw9XHieotbST', 745 | '31h1vYVSYuKP6AhS86fbRdMw9XHiiQ93Mb', 746 | '339cjcWXDj6ZWt9KBp4YxPKJ8BNH7gn2Nw', 747 | '339cjcWXDj6ZWt9KBp4YxPKJ8BNH14Nnx4', 748 | ], 749 | [ 750 | 'mvWRFPELmpCHSkFQ7o9EVdCd9eXeUTa9T8', 751 | 'mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8', 752 | 'mfWxJ45yp2SFn7UciZyNpvDKrzbi36LaVX', 753 | 'mgyZ7874UrDSCpvVnHNLMgAgqegGZBks3w', 754 | 'mgyZ7874UrDSCpvVnHNLMgAgqegGQUXx9c', 755 | ], 756 | [ 757 | '2N8EgwcZq89akxb6mCTTKiHLVeXRpxjuy98', 758 | '2MsFDzHRUAMpjHxKyoEHU3aMCMsVtMqs1PV', 759 | '2MsFDzHRUAMpjHxKyoEHU3aMCMsVtXMsfu8', 760 | '2MthpoMSYqBbuifmrrwgRaLJZLXaSyK2Rai', 761 | '2MthpoMSYqBbuifmrrwgRaLJZLXaSoxBM5T', 762 | ], 763 | ]; 764 | 765 | test('c32ToB58 and b58ToC32', t => { 766 | t.plan(hexStrings.length * versions.length * 2); 767 | for (let i = 0; i < hexStrings.length; i++) { 768 | for (let j = 0; j < versions.length; j++) { 769 | const b58 = c32ToB58(c32addresses[j][i], versions[j]); 770 | const c32 = b58ToC32(b58addresses[j][i], versions[j]); 771 | t.equal(b58, b58addresses[j][i], `c32ToB58: expect ${b58addresses[j][i]}, got ${b58}`); 772 | t.equal(c32, c32addresses[j][i], `b58ToC32: expect ${c32addresses[j][i]}, got ${c32}`); 773 | } 774 | } 775 | }); 776 | 777 | test('c32ToB58 and b58ToC32 equivalent versions', t => { 778 | t.plan(hexStrings.length * equivalentVersions.length * 2); 779 | for (let i = 0; i < hexStrings.length; i++) { 780 | for (let j = 0; j < equivalentVersions.length; j++) { 781 | const b58 = c32ToB58(c32addressesEquivalentVersion[j][i]); 782 | const c32 = b58ToC32(b58addressesEquivalentVersion[j][i]); 783 | t.equal( 784 | b58, 785 | b58addressesEquivalentVersion[j][i], 786 | `c32ToB58: expect ${b58addressesEquivalentVersion[j][i]}, got ${b58}` 787 | ); 788 | t.equal( 789 | c32, 790 | c32addressesEquivalentVersion[j][i], 791 | `b58ToC32: expect ${c32addressesEquivalentVersion[j][i]}, got ${c32}` 792 | ); 793 | } 794 | } 795 | }); 796 | 797 | test('README examples with legacy Buffer', t => { 798 | let version, b58addr; 799 | t.plan(15); 800 | 801 | // ## c32encode, c32decode 802 | t.equal(c32check.c32encode(Buffer.from('hello world').toString('hex')), '38CNP6RVS0EXQQ4V34'); 803 | t.equal(c32check.c32decode('38CNP6RVS0EXQQ4V34'), '68656c6c6f20776f726c64'); 804 | t.equal(Buffer.from('68656c6c6f20776f726c64', 'hex').toString(), 'hello world'); 805 | 806 | // ## c32checkEncode, c32checkDecode 807 | version = 12; 808 | t.equal( 809 | c32check.c32checkEncode(version, Buffer.from('hello world').toString('hex')), 810 | 'CD1JPRV3F41VPYWKCCGRMASC8' 811 | ); 812 | t.equal(c32check.c32checkDecode('CD1JPRV3F41VPYWKCCGRMASC8')[0], 12); 813 | t.equal(c32check.c32checkDecode('CD1JPRV3F41VPYWKCCGRMASC8')[1], '68656c6c6f20776f726c64'); 814 | t.equal(Buffer.from('68656c6c6f20776f726c64', 'hex').toString(), 'hello world'); 815 | 816 | // ## c32address, c32addressDecode 817 | version = 22; 818 | const hash160 = 'a46ff88886c2ef9762d970b4d2c63678835bd39d'; 819 | t.equal(c32check.versions.mainnet.p2pkh, version); 820 | t.equal(c32check.c32address(version, hash160), 'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7'); 821 | t.equal(c32check.c32addressDecode('SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7')[0], version); 822 | t.equal(c32check.c32addressDecode('SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7')[1], hash160); 823 | 824 | // ## c32ToB58, b58ToC32 825 | b58addr = '16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg'; 826 | t.equal(c32check.b58ToC32(b58addr), 'SPWNYDJ3STG7XH7ERWXMV6MQ7Q6EATWVY5Q1QMP8'); 827 | t.equal( 828 | c32check.c32ToB58('SPWNYDJ3STG7XH7ERWXMV6MQ7Q6EATWVY5Q1QMP8'), 829 | '16EMaNw3pkn3v6f2BgnSSs53zAKH4Q8YJg' 830 | ); 831 | b58addr = '3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r'; 832 | t.equal(c32check.b58ToC32(b58addr), 'SM1Y6EXF21RZ9739DFTEQKB1H044BMM0XVCM4A4NY'); 833 | t.equal( 834 | c32check.c32ToB58('SM1Y6EXF21RZ9739DFTEQKB1H044BMM0XVCM4A4NY'), 835 | '3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r' 836 | ); 837 | }); 838 | 839 | const invalidEncodeParameterTypes = [ 840 | [{} as string, 'abc'], 841 | ['abc', {} as string], 842 | [{} as string, {} as string], 843 | ]; 844 | 845 | test('encode throws on invalid types', t => { 846 | t.plan(invalidEncodeParameterTypes.length); 847 | 848 | for (const [p1, p2] of invalidEncodeParameterTypes) { 849 | try { 850 | encode(p1, p2); 851 | t.ok(false, 'encode returned on invalid type'); 852 | } catch (e) { 853 | t.ok(true, 'encode threw error on invalid type'); 854 | } 855 | } 856 | }); 857 | } 858 | 859 | if (process.env.BIG_DATA_TESTS) { 860 | c32encodingRandomBytes(); 861 | } else { 862 | c32encodingTests(); 863 | c32checkEncodingTests(); 864 | c32addressTests(); 865 | c32ToB58Test(); 866 | } 867 | -------------------------------------------------------------------------------- /tests/unitTests/src/tape-promise.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tape-promise/tape' { 2 | export = tape; 3 | 4 | /** 5 | * Create a new test with an optional name string and optional opts object. 6 | * cb(t) fires with the new test object t once all preceeding tests have finished. 7 | * Tests execute serially. 8 | */ 9 | function tape(name: string, cb: tape.TestCase): void; 10 | function tape(name: string, opts: tape.TestOptions, cb: tape.TestCase): void; 11 | function tape(cb: tape.TestCase): void; 12 | function tape(opts: tape.TestOptions, cb: tape.TestCase): void; 13 | 14 | interface TestCb { 15 | (t: tape.TestCase): Promise; 16 | } 17 | function tape(name: string, cb: TestCb): void; 18 | 19 | namespace tape { 20 | interface TestCase { 21 | (test: Test): void; 22 | } 23 | 24 | /** 25 | * Available opts options for the tape function. 26 | */ 27 | interface TestOptions { 28 | skip?: boolean; // See tape.skip. 29 | timeout?: number; // Set a timeout for the test, after which it will fail. See tape.timeoutAfter. 30 | } 31 | 32 | /** 33 | * Options for the createStream function. 34 | */ 35 | interface StreamOptions { 36 | objectMode?: boolean; 37 | } 38 | 39 | /** 40 | * Generate a new test that will be skipped over. 41 | */ 42 | export function skip(name: string, cb: tape.TestCase): void; 43 | export function skip(name: string, opts: tape.TestOptions, cb: tape.TestCase): void; 44 | export function skip(cb: tape.TestCase): void; 45 | export function skip(opts: tape.TestOptions, cb: tape.TestCase): void; 46 | 47 | /** 48 | * The onFinish hook will get invoked when ALL tape tests have finished right before tape is about to print the test summary. 49 | */ 50 | export function onFinish(cb: () => void): void; 51 | 52 | /** 53 | * Like test(name?, opts?, cb) except if you use .only this is the only test case that will run for the entire process, all other test cases using tape will be ignored. 54 | */ 55 | export function only(name: string, cb: tape.TestCase): void; 56 | export function only(name: string, opts: tape.TestOptions, cb: tape.TestCase): void; 57 | export function only(cb: tape.TestCase): void; 58 | export function only(opts: tape.TestOptions, cb: tape.TestCase): void; 59 | 60 | /** 61 | * Create a new test harness instance, which is a function like test(), but with a new pending stack and test state. 62 | */ 63 | export function createHarness(): typeof tape; 64 | /** 65 | * Create a stream of output, bypassing the default output stream that writes messages to console.log(). 66 | * By default stream will be a text stream of TAP output, but you can get an object stream instead by setting opts.objectMode to true. 67 | */ 68 | export function createStream(opts?: tape.StreamOptions): NodeJS.ReadableStream; 69 | 70 | interface Test { 71 | /** 72 | * Create a subtest with a new test handle st from cb(st) inside the current test. 73 | * cb(st) will only fire when t finishes. 74 | * Additional tests queued up after t will not be run until all subtests finish. 75 | */ 76 | test(name: string, cb: tape.TestCase): void; 77 | test(name: string, opts: TestOptions, cb: tape.TestCase): void; 78 | 79 | /** 80 | * Declare that n assertions should be run. end() will be called automatically after the nth assertion. 81 | * If there are any more assertions after the nth, or after end() is called, they will generate errors. 82 | */ 83 | plan(n: number): void; 84 | 85 | /** 86 | * Declare the end of a test explicitly. 87 | * If err is passed in t.end will assert that it is falsey. 88 | */ 89 | end(err?: any): void; 90 | 91 | /** 92 | * Generate a failing assertion with a message msg. 93 | */ 94 | fail(msg?: string): void; 95 | 96 | /** 97 | * Generate a passing assertion with a message msg. 98 | */ 99 | pass(msg?: string): void; 100 | 101 | /** 102 | * Automatically timeout the test after X ms. 103 | */ 104 | timeoutAfter(ms: number): void; 105 | 106 | /** 107 | * Generate an assertion that will be skipped over. 108 | */ 109 | skip(msg?: string): void; 110 | 111 | /** 112 | * Assert that value is truthy with an optional description message msg. 113 | */ 114 | ok(value: any, msg?: string): void; 115 | true(value: any, msg?: string): void; 116 | assert(value: any, msg?: string): void; 117 | 118 | /** 119 | * Assert that value is falsy with an optional description message msg. 120 | */ 121 | notOk(value: any, msg?: string): void; 122 | false(value: any, msg?: string): void; 123 | notok(value: any, msg?: string): void; 124 | 125 | /** 126 | * Assert that err is falsy. 127 | * If err is non-falsy, use its err.message as the description message. 128 | */ 129 | error(err: any, msg?: string): void; 130 | ifError(err: any, msg?: string): void; 131 | ifErr(err: any, msg?: string): void; 132 | iferror(err: any, msg?: string): void; 133 | 134 | /** 135 | * Assert that a === b with an optional description msg. 136 | */ 137 | equal(actual: any, expected: any, msg?: string): void; 138 | equals(actual: any, expected: any, msg?: string): void; 139 | isEqual(actual: any, expected: any, msg?: string): void; 140 | is(actual: any, expected: any, msg?: string): void; 141 | strictEqual(actual: any, expected: any, msg?: string): void; 142 | strictEquals(actual: any, expected: any, msg?: string): void; 143 | 144 | /** 145 | * Assert that a !== b with an optional description msg. 146 | */ 147 | notEqual(actual: any, expected: any, msg?: string): void; 148 | notEquals(actual: any, expected: any, msg?: string): void; 149 | notStrictEqual(actual: any, expected: any, msg?: string): void; 150 | notStrictEquals(actual: any, expected: any, msg?: string): void; 151 | isNotEqual(actual: any, expected: any, msg?: string): void; 152 | isNot(actual: any, expected: any, msg?: string): void; 153 | not(actual: any, expected: any, msg?: string): void; 154 | doesNotEqual(actual: any, expected: any, msg?: string): void; 155 | isInequal(actual: any, expected: any, msg?: string): void; 156 | 157 | /** 158 | * Assert that a and b have the same structure and nested values using node's deepEqual() algorithm with strict comparisons (===) on leaf nodes and an optional description msg. 159 | */ 160 | deepEqual(actual: any, expected: any, msg?: string): void; 161 | deepEquals(actual: any, expected: any, msg?: string): void; 162 | isEquivalent(actual: any, expected: any, msg?: string): void; 163 | same(actual: any, expected: any, msg?: string): void; 164 | 165 | /** 166 | * Assert that a and b do not have the same structure and nested values using node's deepEqual() algorithm with strict comparisons (===) on leaf nodes and an optional description msg. 167 | */ 168 | notDeepEqual(actual: any, expected: any, msg?: string): void; 169 | notEquivalent(actual: any, expected: any, msg?: string): void; 170 | notDeeply(actual: any, expected: any, msg?: string): void; 171 | notSame(actual: any, expected: any, msg?: string): void; 172 | isNotDeepEqual(actual: any, expected: any, msg?: string): void; 173 | isNotDeeply(actual: any, expected: any, msg?: string): void; 174 | isNotEquivalent(actual: any, expected: any, msg?: string): void; 175 | isInequivalent(actual: any, expected: any, msg?: string): void; 176 | 177 | /** 178 | * Assert that a and b have the same structure and nested values using node's deepEqual() algorithm with loose comparisons (==) on leaf nodes and an optional description msg. 179 | */ 180 | deepLooseEqual(actual: any, expected: any, msg?: string): void; 181 | looseEqual(actual: any, expected: any, msg?: string): void; 182 | looseEquals(actual: any, expected: any, msg?: string): void; 183 | 184 | /** 185 | * Assert that a and b do not have the same structure and nested values using node's deepEqual() algorithm with loose comparisons (==) on leaf nodes and an optional description msg. 186 | */ 187 | notDeepLooseEqual(actual: any, expected: any, msg?: string): void; 188 | notLooseEqual(actual: any, expected: any, msg?: string): void; 189 | notLooseEquals(actual: any, expected: any, msg?: string): void; 190 | 191 | /** 192 | * Assert that the function call fn() throws an exception. 193 | * expected, if present, must be a RegExp or Function, which is used to test the exception object. 194 | */ 195 | throws(fn: () => void, msg?: string): void; 196 | throws(fn: () => void, exceptionExpected: RegExp | Function, msg?: string): void; 197 | 198 | /** 199 | * Assert that the function call fn() does not throw an exception. 200 | */ 201 | doesNotThrow(fn: () => void, msg?: string): void; 202 | doesNotThrow(fn: () => void, exceptionExpected: RegExp | Function, msg?: string): void; 203 | 204 | /** 205 | * Print a message without breaking the tap output. 206 | * (Useful when using e.g. tap-colorize where output is buffered & console.log will print in incorrect order vis-a-vis tap output.) 207 | */ 208 | comment(msg: string): void; 209 | 210 | rejects( 211 | fn: Promise | (() => Promise), 212 | exceptionExpected: RegExp | Function, 213 | msg?: string 214 | ): Promise; 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": true, 8 | }, 9 | "include": [ 10 | "./src/**/*", 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = env => { 2 | env = env || {}; 3 | const opts = { 4 | entry: './src/index.ts', 5 | node: false, 6 | devtool: 'source-map', 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.ts?$/, 11 | exclude: /node_modules/, 12 | use: { loader: 'ts-loader' }, 13 | }, 14 | ], 15 | }, 16 | resolve: { extensions: ['.ts', '.js'] }, 17 | output: { 18 | filename: 'c32check.js', 19 | path: require('path').resolve(__dirname, 'dist'), 20 | library: 'c32check', 21 | libraryTarget: 'umd', 22 | globalObject: 'this' 23 | }, 24 | plugins: [], 25 | }; 26 | 27 | if (process.env.ANALYZE || env.ANALYZE) { 28 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 29 | opts.plugins.push(new BundleAnalyzerPlugin()); 30 | } 31 | 32 | return opts; 33 | }; 34 | --------------------------------------------------------------------------------