├── .ava.bench.config.js ├── .changeset ├── README.md ├── config.json └── orange-suits-impress.md ├── .cspell.json ├── .dockerignore ├── .eslintrc ├── .github ├── CONTRIBUTING.md └── workflows │ ├── benchmark.yml │ ├── ci.yml │ ├── doc.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pnp.cjs ├── .pnp.loader.mjs ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── assets ├── libauth-small.png ├── libauth-smaller.png ├── libauth.png ├── libauth.svg └── schemas │ └── wallet-template-v0.schema.json ├── config └── api-extractor.json ├── docs ├── addresses.md ├── crypto.md ├── errors.md ├── index.md ├── install.md ├── keys.md ├── verify-transactions.md └── wallets.md ├── package.json ├── src ├── index.ts ├── lib │ ├── address │ │ ├── address.ts │ │ ├── base58-address.spec.ts │ │ ├── base58-address.ts │ │ ├── bech32.spec.ts │ │ ├── bech32.ts │ │ ├── cash-address.spec.ts │ │ ├── cash-address.ts │ │ ├── fixtures │ │ │ ├── cashaddr.json │ │ │ ├── key_io_invalid.json │ │ │ └── key_io_valid.json │ │ ├── locking-bytecode.spec.ts │ │ └── locking-bytecode.ts │ ├── bin │ │ ├── bin.ts │ │ ├── hashes.ts │ │ ├── ripemd160 │ │ │ ├── ripemd160.base64.ts │ │ │ └── ripemd160.wasm │ │ ├── secp256k1 │ │ │ ├── secp256k1-wasm-types.ts │ │ │ ├── secp256k1-wasm.spec.ts │ │ │ ├── secp256k1-wasm.ts │ │ │ ├── secp256k1.base64.ts │ │ │ └── secp256k1.wasm │ │ ├── sha1 │ │ │ ├── sha1.base64.ts │ │ │ └── sha1.wasm │ │ ├── sha256 │ │ │ ├── sha256.base64.ts │ │ │ └── sha256.wasm │ │ └── sha512 │ │ │ ├── sha512.base64.ts │ │ │ └── sha512.wasm │ ├── compiler │ │ ├── compiler-bch │ │ │ ├── compiler-bch-operations.spec.ts │ │ │ ├── compiler-bch.e2e.built-in-variables.spec.ts │ │ │ ├── compiler-bch.e2e.data-signatures.spec.ts │ │ │ ├── compiler-bch.e2e.evaluations.spec.ts │ │ │ ├── compiler-bch.e2e.hd-key.spec.ts │ │ │ ├── compiler-bch.e2e.key.spec.ts │ │ │ ├── compiler-bch.e2e.langauge.spec.ts │ │ │ ├── compiler-bch.e2e.p2sh.spec.ts │ │ │ ├── compiler-bch.e2e.signing-serialization-algorithms.spec.ts │ │ │ ├── compiler-bch.e2e.spec.helper.ts │ │ │ ├── compiler-bch.e2e.variables.spec.ts │ │ │ ├── compiler-bch.spec.ts │ │ │ └── compiler-bch.ts │ │ ├── compiler-defaults.ts │ │ ├── compiler-operation-helpers.spec.ts │ │ ├── compiler-operation-helpers.ts │ │ ├── compiler-operations.ts │ │ ├── compiler-types.ts │ │ ├── compiler-utils.ts │ │ ├── compiler.spec.ts │ │ ├── compiler.ts │ │ ├── p2pkh-utils.spec.ts │ │ ├── p2pkh-utils.ts │ │ ├── scenarios.spec.ts │ │ ├── scenarios.ts │ │ └── standard │ │ │ ├── p2pkh.spec.ts │ │ │ ├── p2pkh.ts │ │ │ └── standard.ts │ ├── crypto │ │ ├── combinations.spec.ts │ │ ├── combinations.ts │ │ ├── crypto.ts │ │ ├── default-crypto-instances.ts │ │ ├── dependencies.ts │ │ ├── hash.bench.helper.ts │ │ ├── hash.browser.bench.helper.ts │ │ ├── hash.browser.bench.ts │ │ ├── hash.spec.helper.ts │ │ ├── hmac.spec.ts │ │ ├── hmac.ts │ │ ├── pbkdf2.spec.ts │ │ ├── pbkdf2.ts │ │ ├── ripemd160.bench.ts │ │ ├── ripemd160.spec.ts │ │ ├── ripemd160.ts │ │ ├── secp256k1-types.ts │ │ ├── secp256k1.bench.ts │ │ ├── secp256k1.spec.ts │ │ ├── secp256k1.ts │ │ ├── sha1.bench.ts │ │ ├── sha1.spec.ts │ │ ├── sha1.ts │ │ ├── sha256.bench.ts │ │ ├── sha256.spec.ts │ │ ├── sha256.ts │ │ ├── sha512.bench.ts │ │ ├── sha512.spec.ts │ │ └── sha512.ts │ ├── docs.spec.ts │ ├── engine │ │ ├── engine.ts │ │ └── types │ │ │ ├── bcmr-types.ts │ │ │ └── template-types.ts │ ├── format │ │ ├── base-convert.spec.ts │ │ ├── base-convert.ts │ │ ├── base64.spec.ts │ │ ├── base64.ts │ │ ├── bin-string.spec.ts │ │ ├── bin-string.ts │ │ ├── error.spec.ts │ │ ├── error.ts │ │ ├── fixtures │ │ │ └── base58_encode_decode.json │ │ ├── format.ts │ │ ├── hex.spec.ts │ │ ├── hex.ts │ │ ├── log.spec.ts │ │ ├── log.ts │ │ ├── number.bench.ts │ │ ├── number.spec.ts │ │ ├── number.ts │ │ ├── read.spec.ts │ │ ├── read.ts │ │ ├── time.spec.ts │ │ ├── time.ts │ │ ├── type-utils.ts │ │ ├── utf8.spec.ts │ │ └── utf8.ts │ ├── key │ │ ├── bip39.spec.ts │ │ ├── bip39.ts │ │ ├── fixtures │ │ │ ├── bip39.extended-vectors.json │ │ │ └── bip39.trezor.json │ │ ├── hd-key.spec.ts │ │ ├── hd-key.ts │ │ ├── key-utils.spec.ts │ │ ├── key-utils.ts │ │ ├── key.ts │ │ ├── wallet-import-format.spec.ts │ │ ├── wallet-import-format.ts │ │ └── word-lists │ │ │ ├── bip39.chinese-simplified.ts │ │ │ ├── bip39.chinese-traditional.ts │ │ │ ├── bip39.czech.ts │ │ │ ├── bip39.english.ts │ │ │ ├── bip39.french.ts │ │ │ ├── bip39.italian.ts │ │ │ ├── bip39.japanese.ts │ │ │ ├── bip39.korean.ts │ │ │ ├── bip39.portuguese.ts │ │ │ └── bip39.spanish.ts │ ├── language │ │ ├── compile.spec.ts │ │ ├── compile.ts │ │ ├── language-types.ts │ │ ├── language-utils.spec.ts │ │ ├── language-utils.ts │ │ ├── language.ts │ │ ├── parse.spec.ts │ │ ├── parse.ts │ │ ├── parsimmon.ts │ │ ├── reduce.spec.ts │ │ ├── reduce.ts │ │ ├── resolve.spec.ts │ │ └── resolve.ts │ ├── lib.ts │ ├── mappings.spec.ts │ ├── message │ │ ├── fixtures │ │ │ ├── token-prefix-invalid.json │ │ │ └── token-prefix-valid.json │ │ ├── message.ts │ │ ├── read-components.spec.ts │ │ ├── read-components.ts │ │ ├── transaction-encoding.spec.ts │ │ ├── transaction-encoding.ts │ │ └── transaction-types.ts │ ├── schema │ │ ├── README.md │ │ ├── ajv │ │ │ ├── ajv-types.d.ts │ │ │ ├── ajv-utils.ts │ │ │ ├── validate-bcmr.d.ts │ │ │ ├── validate-bcmr.js │ │ │ ├── validate-wallet-template.d.ts │ │ │ └── validate-wallet-template.js │ │ ├── bcmr.schema.json │ │ ├── bcmr.spec.ts │ │ ├── bcmr.ts │ │ ├── fixtures │ │ │ └── bcmr │ │ │ │ ├── bcmr.art-collection.json │ │ │ │ ├── bcmr.decentralized-application.json │ │ │ │ ├── bcmr.fungible-token.json │ │ │ │ └── bcmr.payouts-or-dividends.json │ │ ├── schema.ts │ │ ├── wallet-template.schema.json │ │ ├── wallet-template.spec.ts │ │ └── wallet-template.ts │ ├── transaction │ │ ├── estimate-transaction.ts │ │ ├── fixtures │ │ │ ├── generate-templates.spec.helper.ts │ │ │ ├── template.2-of-2-recoverable.spec.helper.ts │ │ │ ├── template.2-of-3.spec.helper.ts │ │ │ ├── template.cash-channels-v1.spec.helper.ts │ │ │ ├── template.sig-of-sig.spec.helper.ts │ │ │ └── templates │ │ │ │ ├── 2-of-2-recoverable.json │ │ │ │ ├── 2-of-3.json │ │ │ │ ├── cash-channels-v1.json │ │ │ │ ├── p2pkh.json │ │ │ │ └── sig-of-sig.json │ │ ├── generate-transaction.spec.ts │ │ ├── generate-transaction.ts │ │ ├── transaction-e2e.2-of-2-recoverable.spec.ts │ │ ├── transaction-e2e.2-of-3.spec.ts │ │ ├── transaction-e2e.p2pkh.spec.ts │ │ ├── transaction-e2e.sig-of-sig.spec.ts │ │ ├── transaction-e2e.spec.helper.ts │ │ ├── transaction-e2e.templates.spec.ts │ │ └── transaction.ts │ ├── vm │ │ ├── instruction-sets │ │ │ ├── bch │ │ │ │ ├── 2022 │ │ │ │ │ ├── bch-2022-descriptions.ts │ │ │ │ │ ├── bch-2022-errors.ts │ │ │ │ │ ├── bch-2022-instruction-set.ts │ │ │ │ │ ├── bch-2022-opcodes.ts │ │ │ │ │ ├── bch-2022-types.ts │ │ │ │ │ ├── bch-2022-vm.ts │ │ │ │ │ └── bch-2022.ts │ │ │ │ ├── 2023 │ │ │ │ │ ├── bch-2023-consensus.ts │ │ │ │ │ ├── bch-2023-crypto.ts │ │ │ │ │ ├── bch-2023-descriptions.ts │ │ │ │ │ ├── bch-2023-errors.ts │ │ │ │ │ ├── bch-2023-instruction-set.ts │ │ │ │ │ ├── bch-2023-opcodes.ts │ │ │ │ │ ├── bch-2023-tokens.ts │ │ │ │ │ ├── bch-2023-vm.ts │ │ │ │ │ └── bch-2023.ts │ │ │ │ └── chips │ │ │ │ │ ├── bch-chips-crypto.ts │ │ │ │ │ ├── bch-chips-descriptions.ts │ │ │ │ │ ├── bch-chips-errors.ts │ │ │ │ │ ├── bch-chips-instruction-set.ts │ │ │ │ │ ├── bch-chips-loops.ts │ │ │ │ │ ├── bch-chips-opcodes.ts │ │ │ │ │ ├── bch-chips-types.ts │ │ │ │ │ ├── bch-chips-vm.ts │ │ │ │ │ └── bch-chips.ts │ │ │ ├── btc │ │ │ │ ├── btc-descriptions.ts │ │ │ │ ├── btc-opcodes.ts │ │ │ │ ├── btc-types.ts │ │ │ │ └── btc.ts │ │ │ ├── common │ │ │ │ ├── arithmetic.ts │ │ │ │ ├── bitwise.ts │ │ │ │ ├── combinators.ts │ │ │ │ ├── common-types.ts │ │ │ │ ├── common.ts │ │ │ │ ├── consensus.ts │ │ │ │ ├── crypto.ts │ │ │ │ ├── encoding.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── flow-control.ts │ │ │ │ ├── format.ts │ │ │ │ ├── inspection.ts │ │ │ │ ├── instruction-sets-types.ts │ │ │ │ ├── instruction-sets-utils.spec.ts │ │ │ │ ├── instruction-sets-utils.ts │ │ │ │ ├── nop.ts │ │ │ │ ├── push.spec.ts │ │ │ │ ├── push.ts │ │ │ │ ├── signing-serialization.spec.ts │ │ │ │ ├── signing-serialization.ts │ │ │ │ ├── stack.ts │ │ │ │ ├── time.ts │ │ │ │ └── types.spec.ts │ │ │ ├── instruction-sets.ts │ │ │ ├── readme.md │ │ │ └── xec │ │ │ │ ├── fixtures │ │ │ │ └── satoshi-client │ │ │ │ │ ├── bitcoin-satoshi-utils.ts │ │ │ │ │ ├── script-tests-addendum.json │ │ │ │ │ ├── script_tests.json │ │ │ │ │ ├── sighash.json │ │ │ │ │ ├── tx_invalid.json │ │ │ │ │ └── tx_valid.json │ │ │ │ ├── xec-descriptions.ts │ │ │ │ ├── xec-instruction-set.ts │ │ │ │ ├── xec-opcodes.ts │ │ │ │ ├── xec-types.ts │ │ │ │ ├── xec-vm-number-operations.ts │ │ │ │ ├── xec-vm.ts │ │ │ │ ├── xec.script-tests.spec.ts │ │ │ │ ├── xec.spec.ts │ │ │ │ └── xec.ts │ │ ├── virtual-machine.spec.ts │ │ ├── virtual-machine.ts │ │ ├── vm-types.ts │ │ └── vm.ts │ └── vmb-tests │ │ ├── bch-vmb-test-mixins.ts │ │ ├── bch-vmb-test-utils.spec.ts │ │ ├── bch-vmb-test-utils.ts │ │ ├── bch-vmb-test.spec.helper.ts │ │ ├── bch-vmb-tests-invalid.spec.helper.ts │ │ ├── bch-vmb-tests.cashtokens.ts │ │ ├── bch-vmb-tests.live.spec.helper.ts │ │ ├── bch-vmb-tests.signing-serialization.ts │ │ ├── bch-vmb-tests.spec.helper.ts │ │ ├── bch-vmb-tests.spec.ts │ │ ├── bch-vmb-tests.ts │ │ ├── generated │ │ └── bch │ │ │ ├── CHIPs │ │ │ ├── bch_vmb_tests_before_chip_cashtokens_invalid.json │ │ │ ├── bch_vmb_tests_before_chip_cashtokens_invalid_reasons.json │ │ │ ├── bch_vmb_tests_before_chip_cashtokens_nonstandard.json │ │ │ ├── bch_vmb_tests_before_chip_cashtokens_nonstandard_reasons.json │ │ │ ├── bch_vmb_tests_before_chip_cashtokens_standard.json │ │ │ ├── bch_vmb_tests_chip_cashtokens_invalid.json │ │ │ ├── bch_vmb_tests_chip_cashtokens_invalid_reasons.json │ │ │ ├── bch_vmb_tests_chip_cashtokens_nonstandard.json │ │ │ ├── bch_vmb_tests_chip_cashtokens_nonstandard_reasons.json │ │ │ ├── bch_vmb_tests_chip_cashtokens_standard.json │ │ │ ├── bch_vmb_tests_chip_loops_invalid.json │ │ │ ├── bch_vmb_tests_chip_loops_invalid_reasons.json │ │ │ ├── bch_vmb_tests_chip_loops_nonstandard.json │ │ │ ├── bch_vmb_tests_chip_loops_nonstandard_reasons.json │ │ │ └── bch_vmb_tests_chip_loops_standard.json │ │ │ ├── bch_vmb_tests.json │ │ │ ├── bch_vmb_tests_2022_invalid.json │ │ │ ├── bch_vmb_tests_2022_invalid_reasons.json │ │ │ ├── bch_vmb_tests_2022_nonstandard.json │ │ │ ├── bch_vmb_tests_2022_nonstandard_reasons.json │ │ │ └── bch_vmb_tests_2022_standard.json │ │ ├── readme.md │ │ └── vmb-tests.ts └── types │ ├── bitcore-lib-cash.d.ts │ ├── chuhai.d.ts │ ├── rollup-plugin-alias.d.ts │ ├── rollup-plugin-common-js.d.ts │ └── secp256k1.d.ts ├── tsconfig.json ├── wasm ├── docker │ ├── hashes.Dockerfile │ └── secp256k1.Dockerfile └── hashes │ ├── ripemd160 │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── sha1 │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── sha256 │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── sha512 │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── lib.rs └── yarn.lock /.ava.bench.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | files: ['build/**/*.bench.js'], 3 | workerThreads: false, 4 | verbose: true, 5 | }; 6 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "bitauth/libauth" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/orange-suits-impress.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@bitauth/libauth': patch 3 | --- 4 | 5 | clarify `generateDeterministicEntropy` usage examples 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .circleci 2 | .git 3 | .github 4 | .nyc_output 5 | .vscode 6 | build 7 | config 8 | coverage 9 | node_modules 10 | src/**.js 11 | .cspell.json 12 | .eslintrc.json 13 | .gitignore 14 | .gitmodules 15 | .npmignore 16 | .prettierignore 17 | wasm/docker 18 | CHANGELOG.md 19 | LICENSE 20 | package.json 21 | README.md 22 | tsconfig.json 23 | yarn.lock 24 | *.log -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { "project": "./tsconfig.json" }, 5 | "env": { "es6": true }, 6 | "ignorePatterns": [ 7 | "node_modules", 8 | "build", 9 | "coverage", 10 | "src/lib/schema/ajv/*.js" 11 | ], 12 | "extends": ["bitauth"], 13 | // "globals": { "BigInt": true, "console": true, "WebAssembly": true }, 14 | "rules": { 15 | "@typescript-eslint/no-unsafe-enum-comparison": "off" 16 | }, 17 | "overrides": [ 18 | /* 19 | * Require all test files to import functionality from the entry point 20 | * (to test that exports are available to consumers as expected). 21 | */ 22 | { 23 | "files": ["**.spec.ts", "**.bench.ts"], 24 | "rules": { 25 | "import/no-restricted-paths": [ 26 | "error", 27 | { 28 | "zones": [ 29 | { 30 | "target": "./src", 31 | "from": "./src/lib", 32 | "except": ["lib.ts", "(.*).helper.ts", "(.*).json"] 33 | } 34 | ] 35 | } 36 | ], 37 | "@typescript-eslint/naming-convention": "off", 38 | "@typescript-eslint/no-magic-numbers": "off", 39 | "functional/no-expression-statements": "off", 40 | "functional/no-conditional-statements": "off", 41 | "functional/functional-parameters": "off", 42 | "functional/immutable-data": "off", 43 | "functional/no-return-void": "off" 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Libauth Benchmarks 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | permissions: 8 | contents: write 9 | deployments: write 10 | 11 | concurrency: 12 | group: 'benchmarks' 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | benchmark: 17 | name: Run Benchmarks 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | with: 22 | submodules: 'true' 23 | - name: Setup node 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: 20 27 | - name: Run benchmark 28 | run: yarn && yarn playwright install && yarn bench 29 | 30 | # TODO: Libauth-only benchmarks: test for performance regressions in Libauth functionality, format results to work with the below configuration 31 | 32 | # - name: Push benchmark result to gh-pages 33 | # uses: benchmark-action/github-action-benchmark@3d3bca03e83647895ef4f911fa57de3c7a391aaf 34 | # with: 35 | # name: Benchmark.js Benchmark 36 | # tool: 'benchmarkjs' 37 | # output-file-path: bench.log 38 | # github-token: ${{ secrets.GITHUB_TOKEN }} 39 | # auto-push: true 40 | # # Show alert with commit comment on detecting possible performance regression 41 | # alert-threshold: '200%' 42 | # comment-on-alert: true 43 | # fail-on-alert: true 44 | # alert-comment-cc-users: '@bitjson' 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Lint, Build, and Test Libauth 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build-and-test: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | node-version: [18, 20, 22] 11 | runner_index: [0, 1, 2, 3] 12 | runners_per_version: [4] 13 | name: Test Node ${{ matrix.node-version }} (${{ matrix.runner_index }}) 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | submodules: 'recursive' 18 | - name: Install Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: yarn install --immutable --immutable-cache 23 | - run: yarn build 24 | - run: yarn test:unit 25 | env: 26 | CI_NODE_INDEX: ${{ matrix.runner_index }} 27 | CI_NODE_TOTAL: ${{ matrix.runners_per_version }} 28 | - run: yarn cov:lcov 29 | if: ${{ matrix.node-version }} == 20 30 | - name: Upload test coverage 31 | if: ${{ matrix.node-version }} == 20 32 | uses: codecov/codecov-action@v4 33 | with: 34 | fail_ci_if_error: true 35 | token: ${{ secrets.CODECOV_TOKEN }} 36 | verbose: true 37 | 38 | check-policies: 39 | runs-on: ubuntu-latest 40 | name: Check Policies 41 | steps: 42 | - name: Check out repository code 43 | uses: actions/checkout@v4 44 | with: 45 | submodules: 'recursive' 46 | - name: Install Node.js 47 | uses: actions/setup-node@v4 48 | with: 49 | node-version: '20' 50 | - run: yarn install --immutable --immutable-cache --check-cache 51 | - run: yarn build 52 | - run: yarn test:policies 53 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Generate API Docs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: 'pages' 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | doc: 19 | name: Generate API Docs 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | submodules: 'true' 25 | - name: Setup node 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: 20 29 | - name: Prepare dependencies 30 | run: yarn 31 | - name: Generate Docs 32 | run: yarn doc:html 33 | - name: Copy assets 34 | run: yarn doc:assets 35 | - name: Setup Pages 36 | uses: actions/configure-pages@v3 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v1 39 | with: 40 | path: 'build/docs' 41 | - name: Deploy to GitHub Pages 42 | id: github-pages 43 | uses: actions/deploy-pages@v1 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - next 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | id-token: write 17 | issues: read 18 | packages: write 19 | pull-requests: write 20 | steps: 21 | - name: Checkout Repo 22 | uses: actions/checkout@v4 23 | with: 24 | submodules: 'recursive' 25 | - name: Install Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | - name: Install Dependencies 30 | run: yarn install --immutable --immutable-cache 31 | - name: Get new version string 32 | run: | 33 | echo NEW_VERSION=$(yarn changeset status --output=release.json && grep -o '"newVersion":\s*"[^"]*' release.json | awk -F': "' '{print $2}' && rm release.json) >> "$GITHUB_ENV" 34 | - name: Create Release Pull Request or Publish to npm 35 | id: changesets 36 | uses: changesets/action@v1 37 | with: 38 | title: 'Release v${{env.NEW_VERSION}}' 39 | commit: 'Release v${{env.NEW_VERSION}}' 40 | version: yarn changeset:version 41 | publish: yarn release 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | test 4 | src/**.js 5 | temp 6 | docs/markdown 7 | coverage 8 | .nyc_output 9 | *.log 10 | report.*.json 11 | scratch 12 | gitignore.* 13 | 14 | src/lib/bin/**/*.html 15 | src/lib/bin/**/*.js 16 | src/lib/bin/**/*.d.ts 17 | src/lib/bin/**/*.wast 18 | src/lib/bin/**/*.wat 19 | src/lib/bin/**/*.map 20 | 21 | package-lock.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wasm/secp256k1"] 2 | path = wasm/secp256k1 3 | url = https://github.com/bitauth/libauth-secp256k1.git 4 | branch = libauth 5 | shallow = true 6 | [submodule ".yarn"] 7 | path = .yarn 8 | url = https://github.com/bitauth/libauth-dependencies.git 9 | shallow = true 10 | [submodule "config/eslint-config-bitauth"] 11 | path = config/eslint-config-bitauth 12 | url = https://github.com/bitauth/eslint-config-bitauth/ 13 | shallow = true 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | config/eslint-config-bitauth 2 | wasm 3 | .yarn 4 | .pnp.* 5 | package.json 6 | src/lib/address/fixtures/key_io_*.json 7 | src/lib/vm/instruction-sets/xec/fixtures/satoshi-client/*.json -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "eamodio.gitlens", 6 | "dtsvet.vscode-wasm", 7 | "gruntfuggly.todo-tree", 8 | "maelvalais.autoconf", 9 | "ms-vscode.cpptools", 10 | "streetsidesoftware.code-spell-checker", 11 | "ms-azuretools.vscode-docker", 12 | "arcanis.vscode-zipfs", 13 | "yzhang.markdown-all-in-one" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | // To debug, make sure a *.spec.ts file is active in the editor, then run a configuration 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Debug Active Spec", 9 | "runtimeExecutable": "yarn", 10 | "runtimeArgs": ["run", "ava"], 11 | "args": ["${file}"], 12 | // "args": ["--serial", "${file}"], 13 | "outputCapture": "std", 14 | "console": "integratedTerminal", 15 | "skipFiles": ["/**"], 16 | "preLaunchTask": "npm: build" 17 | }, 18 | { 19 | // Use this one if you're already running `yarn watch` 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Debug Active Spec (no build)", 23 | "runtimeExecutable": "yarn", 24 | "runtimeArgs": ["run", "ava"], 25 | "args": ["${file}"], 26 | "outputCapture": "std", 27 | "skipFiles": ["/**", "**/node_modules/**"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.userWords": [], // only use words from .cspell.json 3 | "deno.enable": false, 4 | "editor.formatOnSave": true, 5 | "editor.semanticHighlighting.enabled": true, 6 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 7 | "typescript.enablePromptUseWorkspaceTsdk": true, 8 | "cSpell.enabled": true, 9 | "typescript.preferences.importModuleSpecifierEnding": "js", 10 | "search.exclude": { 11 | "**/.yarn": true, 12 | "**/.pnp.*": true 13 | }, 14 | "eslint.nodePath": ".yarn/sdks", 15 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js" 16 | } 17 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | cacheFolder: './.yarn/cache' 2 | enableGlobalCache: false 3 | enableScripts: false 4 | nodeLinker: pnp 5 | yarnPath: .yarn/releases/yarn-4.0.2.cjs 6 | supportedArchitectures: 7 | cpu: 8 | - 'arm64' 9 | - 'x64' 10 | libc: 11 | - 'glibc' 12 | - 'musl' 13 | os: 14 | - 'darwin' 15 | - 'linux' 16 | - 'win32' 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jason Dreyzehner 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 | -------------------------------------------------------------------------------- /assets/libauth-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/assets/libauth-small.png -------------------------------------------------------------------------------- /assets/libauth-smaller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/assets/libauth-smaller.png -------------------------------------------------------------------------------- /assets/libauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/assets/libauth.png -------------------------------------------------------------------------------- /assets/libauth.svg: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Libauth Documentation 2 | 3 | These guides introduce some of the high-level concepts and functionality provided by Libauth. 4 | 5 | - [Installation](./install.md) 6 | - [Handling Errors](./errors.md) 7 | - [Cryptography](./crypto.md) 8 | - [Keys](./keys.md) 9 | - [Addresses](./addresses.md) 10 | - [Verifying Transactions](./verify-transactions.md) 11 | - [Wallets & Transaction Creation](./wallets.md) 12 | 13 | ## More Examples 14 | 15 | In addition to the usage examples in these guides, note that **Libauth includes comprehensive tests that can help demonstrate usage of all functionality**. 16 | 17 | For example, utilities related to hexadecimal-encoded strings are defined in [`hex.ts`](../src/lib/format/hex.ts); for thorough usage examples, see the co-located [`hex.spec.ts`](../src/lib/format/hex.spec.ts). You can also use GitHub search to see how a particular utility is used throughout the library, e.g. [`splitEvery`](https://github.com/search?q=repo%3Abitauth%2Flibauth+splitEvery&type=code). 18 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Welcome! Libauth is designed to be low-level and lightweight: all functionality is exported as simple functions, so your bundler can eliminate the code you don't use. 4 | 5 | Libauth is a [pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), so Node.js v12 or higher is required (or Deno), and [using ESM is recommended](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-move-my-commonjs-project-to-esm). 6 | 7 | ## Node.js Usage 8 | 9 | To get started, install `@bitauth/libauth` in your environment: 10 | 11 | ```sh 12 | npm install @bitauth/libauth 13 | # OR 14 | yarn add @bitauth/libauth 15 | ``` 16 | 17 | And import the functionality you need: 18 | 19 | ```ts 20 | import { secp256k1 } from '@bitauth/libauth'; 21 | import { msgHash, pubkey, sig } from 'somewhere'; 22 | 23 | secp256k1.verifySignatureDERLowS(sig, pubkey, msgHash) 24 | ? console.log('🚀 Signature valid') 25 | : console.log('❌ Signature invalid'); 26 | ``` 27 | 28 | ### Web Usage 29 | 30 | For web projects, a bundler with [dead-code elimination](https://rollupjs.org/guide/en/#tree-shaking) (A.K.A. "tree shaking") is **strongly recommended** – Libauth is designed to minimize application code size, and dead-code elimination will improve load performance in nearly all applications. 31 | 32 | Consider **[Vite](https://vitejs.dev/) (recommended)**, [Parcel](https://parceljs.org/), [Rollup](https://rollupjs.org/), [Webpack](https://webpack.js.org/), or a bundler designed for your web framework. 33 | 34 | ### Deno Usage 35 | 36 | For Deno usage, Libauth can be imported with the `npm:` protocol: 37 | 38 | ```ts 39 | import { hexToBin } from 'npm:@bitauth/libauth'; 40 | 41 | console.log(hexToBin('beef')); 42 | ``` 43 | 44 | ### Using Typescript Types 45 | 46 | **Libauth should work with modern TypeScript projects without any configuration.** 47 | 48 | If you're having trouble, note that Libauth uses [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), [`WebAssembly`](https://developer.mozilla.org/en-US/docs/WebAssembly), and `es2017` features for some functionality. To type-check this library in you application (without [`skipLibCheck`](https://www.typescriptlang.org/tsconfig#skipLibCheck)), your `tsconfig.json` will need a minimum `target` of `es2020` or `lib` must include `es2017` and `esnext.bigint`. If your application is not already importing types for `WebAssembly`, you may also need to add `dom` to `lib`. 49 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/lib.js'; 2 | -------------------------------------------------------------------------------- /src/lib/address/address.ts: -------------------------------------------------------------------------------- 1 | export * from './base58-address.js'; 2 | export * from './bech32.js'; 3 | export * from './cash-address.js'; 4 | export * from './locking-bytecode.js'; 5 | -------------------------------------------------------------------------------- /src/lib/bin/bin.ts: -------------------------------------------------------------------------------- 1 | export * from './hashes.js'; 2 | export * from './ripemd160/ripemd160.base64.js'; 3 | export * from './secp256k1/secp256k1-wasm.js'; 4 | export * from './sha1/sha1.base64.js'; 5 | export * from './sha256/sha256.base64.js'; 6 | export * from './sha512/sha512.base64.js'; 7 | -------------------------------------------------------------------------------- /src/lib/bin/ripemd160/ripemd160.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/src/lib/bin/ripemd160/ripemd160.wasm -------------------------------------------------------------------------------- /src/lib/bin/secp256k1/secp256k1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/src/lib/bin/secp256k1/secp256k1.wasm -------------------------------------------------------------------------------- /src/lib/bin/sha1/sha1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/src/lib/bin/sha1/sha1.wasm -------------------------------------------------------------------------------- /src/lib/bin/sha256/sha256.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/src/lib/bin/sha256/sha256.wasm -------------------------------------------------------------------------------- /src/lib/bin/sha512/sha512.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitauth/libauth/60aec239cc2d57ae21d0069c5bbafb346abc9b66/src/lib/bin/sha512/sha512.wasm -------------------------------------------------------------------------------- /src/lib/compiler/compiler-bch/compiler-bch-operations.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { CompilationContextBCH } from '../../lib.js'; 4 | import { compilerOperationSigningSerializationFullBCH } from '../../lib.js'; 5 | 6 | test('compilerOperationSigningSerializationFullBCH: requires an algorithm', (t) => { 7 | t.deepEqual( 8 | compilerOperationSigningSerializationFullBCH( 9 | '', 10 | { compilationContext: {} as CompilationContextBCH }, 11 | { 12 | scripts: { lock: '' }, 13 | sha256: { hash: () => Uint8Array.of() }, 14 | sourceScriptIds: ['test'], 15 | unlockingScripts: { test: 'lock' }, 16 | }, 17 | ), 18 | { 19 | error: 20 | 'Invalid signing serialization operation. Include the desired component or algorithm, e.g. "signing_serialization.version".', 21 | status: 'error', 22 | }, 23 | ); 24 | }); 25 | test('compilerOperationSigningSerializationFullBCH: error on unknown algorithms', (t) => { 26 | t.deepEqual( 27 | compilerOperationSigningSerializationFullBCH( 28 | 'signing_serialization.full_unknown_serialization', 29 | { compilationContext: {} as CompilationContextBCH }, 30 | { 31 | scripts: { lock: '' }, 32 | sha256: { hash: () => Uint8Array.of() }, 33 | sourceScriptIds: ['test'], 34 | unlockingScripts: { test: 'lock' }, 35 | }, 36 | ), 37 | { 38 | error: 39 | 'Unknown signing serialization algorithm, "full_unknown_serialization".', 40 | status: 'error', 41 | }, 42 | ); 43 | }); 44 | -------------------------------------------------------------------------------- /src/lib/compiler/compiler-bch/compiler-bch.e2e.spec.helper.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { 4 | AuthenticationProgramStateBCH, 5 | BytecodeGenerationResult, 6 | CompilationContextBCH, 7 | CompilationData, 8 | CompilerConfiguration, 9 | CompilerConfigurationBCH, 10 | } from '../../lib.js'; 11 | import { 12 | compilerConfigurationToCompilerBCH, 13 | compilerOperationsBCH, 14 | createAuthenticationProgramEvaluationCommon, 15 | createCompilationContextCommonTesting, 16 | createVirtualMachineBCH, 17 | generateBytecodeMap, 18 | hexToBin, 19 | OpcodesBCH2022, 20 | ripemd160, 21 | secp256k1, 22 | sha256, 23 | sha512, 24 | stringifyTestVector, 25 | } from '../../lib.js'; 26 | 27 | /** 28 | * `m` 29 | */ 30 | export const hdPrivateKey = 31 | 'xprv9s21ZrQH143K2PfMvkNViFc1fgumGqBew45JD8SxA59Jc5M66n3diqb92JjvaR61zT9P89Grys12kdtV4EFVo6tMwER7U2hcUmZ9VfMYPLC'; 32 | /** 33 | * `M` 34 | */ 35 | export const hdPublicKey = 36 | 'xpub661MyMwAqRbcEsjq2muW5PYkDikFgHuWJGzu1WrZiQgHUsgEeKMtGducsZe1iRsGAGNGDzmWYDM69ya24LMyR7mDhtzqQsc286XEQfM2kkV'; 37 | 38 | /** 39 | * HD key at `m/0'` 40 | */ 41 | export const hdPrivateKeyM0H = 42 | 'xprv9uNAm3qC8EoibXd3mwgQ9rxF8XJdfA9V9sF25DpLtYcf1u51Rpf8tfV4n2PdChXM97miXGiJf6UL2SsathXbiVbF6tSgmAVGM3XNb6Yn2EZ'; 43 | 44 | /** 45 | * The public key derived from {@link hdPrivateKeyM0H}. 46 | */ 47 | export const hdPublicKeyM0H = 48 | 'xpub68MXAZN5xcN1p1hWsyDQWztygZ984csLX6AcscDxSt9dthQ9yMyPSToYdJ24jCS5jaVMGSiLeGuP2cWvgKKYQsNXyg988XGGQYgk1FjDv4P'; 49 | 50 | /** 51 | * `m/0` 52 | */ 53 | export const privateKeyM0 = hexToBin( 54 | 'f85d4bd8a03ca106c9deb47b791803dac7f0333809e3f1dd04d182e0aba6e553', 55 | ); 56 | 57 | const vm = createVirtualMachineBCH(); 58 | 59 | /** 60 | * Uses `createCompiler` rather than `createCompilerBCH` for performance. 61 | */ 62 | export const expectCompilationResult = test.macro< 63 | [ 64 | string, 65 | CompilationData, 66 | BytecodeGenerationResult, 67 | CompilerConfiguration['variables']?, 68 | Partial>?, 69 | ] 70 | >( 71 | ( 72 | t, 73 | testScript, 74 | otherData, 75 | expectedResult, 76 | variables, 77 | configurationOverrides, 78 | // eslint-disable-next-line @typescript-eslint/max-params 79 | ) => { 80 | const compiler = compilerConfigurationToCompilerBCH< 81 | CompilerConfigurationBCH, 82 | AuthenticationProgramStateBCH 83 | >({ 84 | createAuthenticationProgram: createAuthenticationProgramEvaluationCommon, 85 | entityOwnership: { 86 | one: 'ownerEntityOne', 87 | owner: 'ownerEntityId', 88 | two: 'ownerEntityTwo', 89 | }, 90 | opcodes: generateBytecodeMap(OpcodesBCH2022), 91 | operations: compilerOperationsBCH, 92 | ripemd160, 93 | scripts: { 94 | another: '0xabcdef', 95 | broken: 'does_not_exist', 96 | lock: '', 97 | test: testScript, 98 | }, 99 | secp256k1, 100 | sha256, 101 | sha512, 102 | unlockingScripts: { 103 | test: 'lock', 104 | }, 105 | variables, 106 | vm, 107 | ...configurationOverrides, 108 | }); 109 | 110 | const resultUnlock = compiler.generateBytecode({ 111 | data: { 112 | compilationContext: createCompilationContextCommonTesting(), 113 | ...otherData, 114 | }, 115 | scriptId: 'test', 116 | }); 117 | return t.deepEqual( 118 | resultUnlock, 119 | expectedResult, 120 | `- \nResult: ${stringifyTestVector( 121 | resultUnlock, 122 | )}\n\nExpected:\n ${stringifyTestVector(expectedResult)}\n`, 123 | ); 124 | }, 125 | ); 126 | -------------------------------------------------------------------------------- /src/lib/compiler/compiler-defaults.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-duplicate-enum-values */ 2 | export enum CompilerDefaults { 3 | /** 4 | * The `addressIndex` used by the default scenario `data`. 5 | */ 6 | defaultScenarioAddressIndex = 0, 7 | /** 8 | * The value used for `["slot"]` and `["copy"]` locking or unlocking bytecode 9 | * when generating a scenario and no `unlockingScriptId` is provided. 10 | */ 11 | // eslint-disable-next-line @typescript-eslint/no-mixed-enums 12 | defaultScenarioBytecode = '', 13 | /** 14 | * 15 | * The value of `currentBlockHeight` in the default wallet template 16 | * scenario. This is the height of the second mined block after the genesis 17 | * block: `000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd`. 18 | * 19 | * This default value was chosen to be low enough to simplify the debugging of 20 | * block height offsets while remaining differentiated from `0` and `1`, which 21 | * are used both as boolean return values and for control flow. 22 | */ 23 | defaultScenarioCurrentBlockHeight = 2, 24 | /** 25 | * The value of `currentBlockTime` in the default wallet template 26 | * scenario. This is the Median Time-Past block time (BIP113) of block `2` 27 | * (the block used in 28 | * {@link CompilerDefaults.defaultScenarioCurrentBlockHeight}). 29 | */ 30 | defaultScenarioCurrentBlockTime = 1231469665, 31 | /** 32 | * The default `outpointTransactionHash` of inputs in scenarios. 33 | */ 34 | defaultScenarioInputOutpointTransactionHash = '0000000000000000000000000000000000000000000000000000000000000001', 35 | /** 36 | * The default `category` of tokens in scenarios. 37 | */ 38 | defaultScenarioOutputTokenCategory = '0000000000000000000000000000000000000000000000000000000000000002', 39 | /** 40 | * The default `sequenceNumber` of inputs in scenarios. 41 | */ 42 | defaultScenarioInputSequenceNumber = 0, 43 | /** 44 | * The default `lockingBytecode` value for scenario outputs, 45 | * `OP_RETURN <"libauth">` (hex: `6a076c696261757468`). 46 | */ 47 | defaultScenarioOutputLockingBytecode = '6a076c696261757468', 48 | /** 49 | * The default `valueSatoshis` of outputs in scenarios. 50 | */ 51 | defaultScenarioOutputValueSatoshis = 0, 52 | /** 53 | * The value of `transaction.locktime` in the default wallet template 54 | * scenario. 55 | */ 56 | defaultScenarioTransactionLocktime = 0, 57 | /** 58 | * The value of `transaction.version` in the default wallet template 59 | * scenario. Transaction version `2` enables `OP_CHECKSEQUENCEVERIFY` as 60 | * described in BIP68, BIP112, and BIP113. 61 | */ 62 | defaultScenarioTransactionVersion = 2, 63 | /** 64 | *s 65 | * If unset, each `HdKey` uses this `addressOffset`. 66 | */ 67 | hdKeyAddressOffset = 0, 68 | /** 69 | * If unset, each `HdKey` uses this `hdPublicKeyDerivationPath`. 70 | */ 71 | hdKeyHdPublicKeyDerivationPath = '', 72 | /** 73 | * If unset, each `HdKey` uses this `privateDerivationPath`. 74 | */ 75 | hdKeyPrivateDerivationPath = 'i', 76 | 77 | /** 78 | * The prefix used to refer to other scenario bytecode scripts from within a 79 | * bytecode script. See {@link WalletTemplateScenarioData.bytecode} 80 | * for details. 81 | */ 82 | scenarioBytecodeScriptPrefix = '_scenario.', 83 | } 84 | -------------------------------------------------------------------------------- /src/lib/compiler/compiler-operation-helpers.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | compilerOperationHelperGenerateCoveredBytecode, 5 | compilerOperationRequires, 6 | stringifyTestVector, 7 | } from '../lib.js'; 8 | 9 | test('attemptCompilerOperations: can skip configuration property check', (t) => { 10 | t.deepEqual( 11 | compilerOperationRequires({ 12 | canBeSkipped: true, 13 | configurationProperties: ['entityOwnership'], 14 | dataProperties: [], 15 | operation: () => ({ error: 'test failed', status: 'error' }), 16 | })('', {}, { scripts: {} }), 17 | { status: 'skip' }, 18 | ); 19 | }); 20 | 21 | test('compilerOperationHelperGenerateCoveredBytecode: empty sourceScriptIds', (t) => { 22 | const result = compilerOperationHelperGenerateCoveredBytecode({ 23 | configuration: { scripts: {} }, 24 | data: {}, 25 | identifier: 'test', 26 | sourceScriptIds: [], 27 | unlockingScripts: {}, 28 | }); 29 | t.deepEqual( 30 | result, 31 | { 32 | error: 33 | 'Identifier "test" requires a signing serialization, but "coveredBytecode" cannot be determined because the compiler configuration\'s "sourceScriptIds" is empty.', 34 | status: 'error', 35 | }, 36 | stringifyTestVector(result), 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/lib/compiler/compiler.ts: -------------------------------------------------------------------------------- 1 | export * from './compiler-bch/compiler-bch.js'; 2 | export * from './compiler-defaults.js'; 3 | export * from './compiler-operation-helpers.js'; 4 | export * from './compiler-operations.js'; 5 | export * from './compiler-types.js'; 6 | export * from './compiler-utils.js'; 7 | export * from './scenarios.js'; 8 | export * from './p2pkh-utils.js'; 9 | export * from './standard/standard.js'; 10 | export * from '../engine/types/template-types.js'; 11 | -------------------------------------------------------------------------------- /src/lib/compiler/standard/p2pkh.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | importWalletTemplate, 5 | walletTemplateP2pkh, 6 | walletTemplateP2pkhNonHd, 7 | } from '../../lib.js'; 8 | 9 | test('walletTemplateP2pkh is valid', (t) => { 10 | const template = importWalletTemplate(walletTemplateP2pkhNonHd); 11 | t.true(typeof template !== 'string'); 12 | }); 13 | 14 | test('walletTemplateP2pkh is mostly equivalent to walletTemplateP2pkhHd', (t) => { 15 | t.deepEqual(walletTemplateP2pkhNonHd.$schema, walletTemplateP2pkh.$schema); 16 | t.deepEqual(walletTemplateP2pkhNonHd.scripts, walletTemplateP2pkh.scripts); 17 | t.deepEqual( 18 | walletTemplateP2pkhNonHd.supported, 19 | walletTemplateP2pkh.supported, 20 | ); 21 | t.deepEqual(walletTemplateP2pkhNonHd.version, walletTemplateP2pkh.version); 22 | }); 23 | 24 | test('walletTemplateP2pkhHd is valid', (t) => { 25 | const template = importWalletTemplate(walletTemplateP2pkh); 26 | t.true(typeof template !== 'string'); 27 | }); 28 | -------------------------------------------------------------------------------- /src/lib/compiler/standard/p2pkh.ts: -------------------------------------------------------------------------------- 1 | import type { WalletTemplate } from '../../lib.js'; 2 | 3 | /** 4 | * A standard single-factor wallet template that uses 5 | * Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use 6 | * on the network. 7 | * 8 | * This P2PKH template uses BCH Schnorr signatures, reducing the size of 9 | * transactions. 10 | * 11 | * Note, this wallet template uses only a single `Key`. For HD key 12 | * support, see {@link walletTemplateP2pkhHd}. 13 | */ 14 | export const walletTemplateP2pkhNonHd: WalletTemplate = { 15 | $schema: 'https://libauth.org/schemas/wallet-template-v0.schema.json', 16 | description: 17 | 'A standard single-factor wallet template that uses Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use on the network.\n\nThis P2PKH template uses BCH Schnorr signatures, reducing the size of transactions.', 18 | entities: { 19 | owner: { 20 | description: 'The individual who can spend from this wallet.', 21 | name: 'Owner', 22 | scripts: ['lock', 'unlock'], 23 | variables: { 24 | key: { 25 | description: 'The private key that controls this wallet.', 26 | name: 'Key', 27 | type: 'Key', 28 | }, 29 | }, 30 | }, 31 | }, 32 | name: 'Single Signature (P2PKH)', 33 | scripts: { 34 | lock: { 35 | lockingType: 'standard', 36 | name: 'P2PKH Lock', 37 | script: 38 | 'OP_DUP\nOP_HASH160 <$( OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG', 39 | }, 40 | unlock: { 41 | name: 'Unlock', 42 | script: '\n', 43 | unlocks: 'lock', 44 | }, 45 | }, 46 | supported: ['BCH_2020_05', 'BCH_2021_05', 'BCH_2022_05'], 47 | version: 0, 48 | }; 49 | 50 | /** 51 | * A standard single-factor wallet template that uses 52 | * Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use 53 | * on the network. 54 | * 55 | * This P2PKH template uses BCH Schnorr signatures, reducing the size of 56 | * transactions. 57 | * 58 | * Because the template uses a Hierarchical Deterministic (HD) key, it also 59 | * supports watch-only clients. 60 | */ 61 | export const walletTemplateP2pkh: WalletTemplate = { 62 | $schema: 'https://libauth.org/schemas/wallet-template-v0.schema.json', 63 | description: 64 | 'A standard single-factor wallet template that uses Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use on the network.\n\nThis P2PKH template uses BCH Schnorr signatures, reducing the size of transactions. Because the template uses a Hierarchical Deterministic (HD) key, it also supports watch-only clients.', 65 | entities: { 66 | owner: { 67 | description: 'The individual who can spend from this wallet.', 68 | name: 'Owner', 69 | scripts: ['lock', 'unlock'], 70 | variables: { 71 | key: { 72 | description: 'The private key that controls this wallet.', 73 | name: 'Key', 74 | type: 'HdKey', 75 | }, 76 | }, 77 | }, 78 | }, 79 | name: 'Single Signature (P2PKH)', 80 | scripts: { 81 | lock: { 82 | lockingType: 'standard', 83 | name: 'P2PKH Lock', 84 | script: 85 | 'OP_DUP\nOP_HASH160 <$( OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG', 86 | }, 87 | unlock: { 88 | name: 'Unlock', 89 | script: '\n', 90 | unlocks: 'lock', 91 | }, 92 | }, 93 | supported: ['BCH_2020_05', 'BCH_2021_05', 'BCH_2022_05'], 94 | version: 0, 95 | }; 96 | -------------------------------------------------------------------------------- /src/lib/compiler/standard/standard.ts: -------------------------------------------------------------------------------- 1 | export * from './p2pkh.js'; 2 | -------------------------------------------------------------------------------- /src/lib/crypto/combinations.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { hash160, hash256, hexToBin, utf8ToBin } from '../lib.js'; 4 | 5 | test('hash160', (t) => { 6 | t.deepEqual( 7 | hash160(hexToBin('')), 8 | hexToBin('b472a266d0bd89c13706a4132ccfb16f7c3b9fcb'), 9 | ); 10 | t.deepEqual( 11 | hash160(utf8ToBin('abc')), 12 | hexToBin('bb1be98c142444d7a56aa3981c3942a978e4dc33'), 13 | ); 14 | }); 15 | 16 | test('hash256', (t) => { 17 | t.deepEqual( 18 | hash256(hexToBin('')), 19 | hexToBin( 20 | '5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456', 21 | ), 22 | ); 23 | t.deepEqual( 24 | hash256(utf8ToBin('abc')), 25 | hexToBin( 26 | '4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358', 27 | ), 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /src/lib/crypto/combinations.ts: -------------------------------------------------------------------------------- 1 | import type { Ripemd160, Sha256 } from '../lib.js'; 2 | 3 | import { 4 | ripemd160 as internalRipemd160, 5 | sha256 as internalSha256, 6 | } from './default-crypto-instances.js'; 7 | 8 | /** 9 | * Hash the given payload with sha256, then hash the 32-byte result with 10 | * ripemd160, returning a 20-byte hash. 11 | * 12 | * This hash is used in both {@link AddressType.p2pkh} and 13 | * {@link AddressType.p2sh20} addresses. 14 | * 15 | * @param payload - the Uint8Array to hash 16 | */ 17 | export const hash160 = ( 18 | payload: Uint8Array, 19 | crypto: { 20 | ripemd160: { hash: Ripemd160['hash'] }; 21 | sha256: { hash: Sha256['hash'] }; 22 | } = { ripemd160: internalRipemd160, sha256: internalSha256 }, 23 | ) => crypto.ripemd160.hash(crypto.sha256.hash(payload)); 24 | 25 | /** 26 | * Hash the given payload with sha256, then hash the 32-byte result with 27 | * one final round of sha256, returning a 32-byte hash. 28 | * 29 | * This type of hash is used to generate identifiers for transactions and blocks 30 | * (and therefore in block mining). 31 | * 32 | * @param payload - the Uint8Array to hash 33 | */ 34 | export const hash256 = ( 35 | payload: Uint8Array, 36 | sha256: { hash: Sha256['hash'] } = internalSha256, 37 | ) => sha256.hash(sha256.hash(payload)); 38 | -------------------------------------------------------------------------------- /src/lib/crypto/crypto.ts: -------------------------------------------------------------------------------- 1 | export * from './combinations.js'; 2 | export * from './default-crypto-instances.js'; 3 | export * from './hmac.js'; 4 | export * from './pbkdf2.js'; 5 | export * from './ripemd160.js'; 6 | export * from './secp256k1.js'; 7 | export * from './secp256k1-types.js'; 8 | export * from './sha1.js'; 9 | export * from './sha256.js'; 10 | export * from './sha512.js'; 11 | -------------------------------------------------------------------------------- /src/lib/crypto/default-crypto-instances.ts: -------------------------------------------------------------------------------- 1 | import { instantiateRipemd160 } from './ripemd160.js'; 2 | import { instantiateSecp256k1 } from './secp256k1.js'; 3 | import { instantiateSha1 } from './sha1.js'; 4 | import { instantiateSha256 } from './sha256.js'; 5 | import { instantiateSha512 } from './sha512.js'; 6 | 7 | const [sha1, sha256, sha512, ripemd160, secp256k1] = await Promise.all([ 8 | instantiateSha1(), 9 | instantiateSha256(), 10 | instantiateSha512(), 11 | instantiateRipemd160(), 12 | instantiateSecp256k1(), 13 | ]); 14 | 15 | export { ripemd160, secp256k1, sha1, sha256, sha512 }; 16 | -------------------------------------------------------------------------------- /src/lib/crypto/dependencies.ts: -------------------------------------------------------------------------------- 1 | export { base64ToBin } from '../format/format.js'; 2 | 3 | export * from '../bin/bin.js'; 4 | -------------------------------------------------------------------------------- /src/lib/crypto/hash.browser.bench.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import test from 'ava'; 4 | 5 | import { chromium } from '@playwright/test'; 6 | import alias from '@rollup/plugin-alias'; 7 | import commonjs from '@rollup/plugin-commonjs'; 8 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 9 | import { rollup } from 'rollup'; 10 | 11 | const prepareCode = async () => { 12 | // eslint-disable-next-line no-console 13 | const realConsoleWarn = console.warn; 14 | /** 15 | * Suppress Rollup warning: 16 | * `Use of eval is strongly discouraged, as it poses security risks and may cause issues with minification` 17 | */ 18 | // eslint-disable-next-line no-console 19 | console.warn = (suppress: string) => suppress; 20 | 21 | const bundle = await rollup({ 22 | input: join( 23 | new URL('.', import.meta.url).pathname, 24 | 'hash.browser.bench.helper.js', 25 | ), 26 | plugins: [ 27 | alias({ 28 | entries: { 29 | chuhai: './../../bench/chuhai.js', 30 | 'hash.js': './../../bench/hash.js', 31 | }, 32 | }), 33 | commonjs(), 34 | nodeResolve(), 35 | ], 36 | }); 37 | // eslint-disable-next-line no-console 38 | console.warn = realConsoleWarn; 39 | 40 | const result = await bundle.generate({ 41 | format: 'esm', 42 | }); 43 | return result.output[0].code; 44 | }; 45 | 46 | const preparePage = async () => { 47 | const browser = await chromium.launch(); 48 | const page = await browser.newPage(); 49 | // https://github.com/GoogleChrome/puppeteer/issues/2301#issuecomment-379622459 50 | await page.goto('file:///'); 51 | return { browser, page }; 52 | }; 53 | 54 | (async () => { 55 | const [code, { browser, page }] = await Promise.all([ 56 | prepareCode(), 57 | preparePage(), 58 | ]); 59 | 60 | test(`# browser: ${browser.version()}`, async (t) => { 61 | page.on('console', (msg) => { 62 | // eslint-disable-next-line no-console 63 | console.log(msg.text()); 64 | }); 65 | 66 | // cspell: disable-next-line 67 | page.on('pageerror', (err) => { 68 | // eslint-disable-next-line no-console 69 | console.error(`pageerror: ${String(err)}`); // cspell: disable-line 70 | }); 71 | 72 | // eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor 73 | await new Promise(async (resolve) => { 74 | await page.exposeFunction('benchError', (error: string) => { 75 | // eslint-disable-next-line no-console 76 | console.error(error); 77 | }); 78 | await page.exposeFunction('benchComplete', async () => { 79 | // eslint-disable-next-line no-console 80 | console.log('Browser benchmark complete, closing browser.'); 81 | await browser.close(); 82 | t.pass(); 83 | resolve(); 84 | }); 85 | await page.setContent(``); 86 | }); 87 | }); 88 | })().catch((err) => { 89 | // eslint-disable-next-line no-console 90 | console.error(err); 91 | }); 92 | -------------------------------------------------------------------------------- /src/lib/crypto/hmac.ts: -------------------------------------------------------------------------------- 1 | import { 2 | sha256 as internalSha256, 3 | sha512 as internalSha512, 4 | } from '../crypto/default-crypto-instances.js'; 5 | import { flattenBinArray } from '../format/format.js'; 6 | import type { Sha256, Sha512 } from '../lib.js'; 7 | 8 | export type HmacFunction = ( 9 | secret: Uint8Array, 10 | message: Uint8Array, 11 | ) => Uint8Array; 12 | 13 | /** 14 | * Instantiate a hash-based message authentication code (HMAC) function as 15 | * specified by RFC 2104. 16 | * 17 | * @param hashFunction - a cryptographic hash function that iterates a basic 18 | * compression function over blocks of data 19 | * @param blockByteLength - the byte-length of blocks used in `hashFunction` 20 | */ 21 | export const instantiateHmacFunction = 22 | ( 23 | hashFunction: (input: Uint8Array) => Uint8Array, 24 | blockByteLength: number, 25 | ): HmacFunction => 26 | (secret, message) => { 27 | const key = new Uint8Array(blockByteLength).fill(0); 28 | // eslint-disable-next-line functional/no-expression-statements 29 | key.set(secret.length > blockByteLength ? hashFunction(secret) : secret, 0); 30 | 31 | const innerPaddingFill = 0x36; 32 | const innerPadding = new Uint8Array(blockByteLength).fill(innerPaddingFill); 33 | // eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion 34 | const innerPrefix = innerPadding.map((pad, index) => pad ^ key[index]!); 35 | const innerContent = flattenBinArray([innerPrefix, message]); 36 | const innerResult = hashFunction(innerContent); 37 | 38 | const outerPaddingFill = 0x5c; 39 | const outerPadding = new Uint8Array(blockByteLength).fill(outerPaddingFill); 40 | // eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion 41 | const outerPrefix = outerPadding.map((pad, index) => pad ^ key[index]!); 42 | return hashFunction(flattenBinArray([outerPrefix, innerResult])); 43 | }; 44 | 45 | const sha256BlockByteLength = 64; 46 | 47 | /** 48 | * Create a hash-based message authentication code using HMAC-SHA256 as 49 | * specified in `RFC 4231`. Returns a 32-byte Uint8Array. 50 | * 51 | * Secrets longer than the block byte-length (64 bytes) are hashed before 52 | * use, shortening their length to the minimum recommended length (32 bytes). 53 | * See `RFC 2104` for details. 54 | * 55 | * @param secret - the secret key (recommended length: 32-64 bytes) 56 | * @param message - the message to authenticate 57 | * @param sha256 - an implementation of Sha256 (defaults to the 58 | * internal WASM implementation) 59 | */ 60 | export const hmacSha256 = ( 61 | secret: Uint8Array, 62 | message: Uint8Array, 63 | sha256: { hash: Sha256['hash'] } = internalSha256, 64 | ) => 65 | instantiateHmacFunction(sha256.hash, sha256BlockByteLength)(secret, message); 66 | 67 | const sha512BlockByteLength = 128; 68 | 69 | /** 70 | * Create a hash-based message authentication code using HMAC-SHA512 as 71 | * specified in `RFC 4231`. Returns a 64-byte Uint8Array. 72 | * 73 | * Secrets longer than the block byte-length (128 bytes) are hashed before 74 | * use, shortening their length to the minimum recommended length (64 bytes). 75 | * See `RFC 2104` for details. 76 | * 77 | * @param secret - the secret key (recommended length: 64-128 bytes) 78 | * @param message - the message to authenticate 79 | * @param sha512 - an implementation of Sha512 (defaults to the 80 | * internal WASM implementation) 81 | */ 82 | export const hmacSha512 = ( 83 | secret: Uint8Array, 84 | message: Uint8Array, 85 | sha512: { hash: Sha512['hash'] } = internalSha512, 86 | ) => 87 | instantiateHmacFunction(sha512.hash, sha512BlockByteLength)(secret, message); 88 | -------------------------------------------------------------------------------- /src/lib/crypto/ripemd160.bench.ts: -------------------------------------------------------------------------------- 1 | import type { Ripemd160 } from '../lib.js'; 2 | import { instantiateRipemd160 } from '../lib.js'; 3 | 4 | import { benchmarkHashingFunction } from './hash.bench.helper.js'; 5 | 6 | benchmarkHashingFunction( 7 | 'ripemd160', 8 | instantiateRipemd160(), 9 | 'ripemd160', 10 | ); 11 | -------------------------------------------------------------------------------- /src/lib/crypto/ripemd160.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Ripemd160 } from '../lib.js'; 2 | import { 3 | getEmbeddedRipemd160Binary, 4 | instantiateRipemd160, 5 | instantiateRipemd160Bytes, 6 | } from '../lib.js'; 7 | 8 | import { testHashFunction } from './hash.spec.helper.js'; 9 | 10 | // prettier-ignore 11 | const abcHash = new Uint8Array([142, 178, 8, 247, 224, 93, 152, 122, 155, 4, 74, 142, 152, 198, 176, 135, 241, 90, 11, 252]); 12 | 13 | // prettier-ignore 14 | const testHash = new Uint8Array([94, 82, 254, 228, 126, 107, 7, 5, 101, 247, 67, 114, 70, 140, 220, 105, 157, 232, 145, 7]); 15 | 16 | // prettier-ignore 17 | const libauthHash = new Uint8Array([110, 49, 102, 23, 96, 92, 29, 1, 244, 107, 255, 233, 7, 87, 156, 120, 131, 157, 28, 239]); 18 | 19 | testHashFunction({ 20 | abcHash, 21 | getEmbeddedBinary: getEmbeddedRipemd160Binary, 22 | hashFunctionName: 'ripemd160', 23 | instantiate: instantiateRipemd160, 24 | instantiateBytes: instantiateRipemd160Bytes, 25 | libauthHash, 26 | nodeJsAlgorithm: 'ripemd160', 27 | testHash, 28 | }); 29 | -------------------------------------------------------------------------------- /src/lib/crypto/ripemd160.ts: -------------------------------------------------------------------------------- 1 | import type { HashFunction } from '../lib.js'; 2 | 3 | import { 4 | base64ToBin, 5 | instantiateRustWasm, 6 | ripemd160Base64Bytes, 7 | } from './dependencies.js'; 8 | 9 | export type Ripemd160 = HashFunction & { 10 | /** 11 | * Finish an incremental ripemd160 hashing computation. 12 | * 13 | * Returns the final hash. 14 | * 15 | * @param rawState - a raw state returned by `update` 16 | */ 17 | final: (rawState: Uint8Array) => Uint8Array; 18 | 19 | /** 20 | * Returns the ripemd160 hash of the provided input. 21 | * 22 | * To incrementally construct a ripemd160 hash (e.g. for streaming), use 23 | * `init`, `update`, and `final`. 24 | * 25 | * @param input - a Uint8Array to be hashed using ripemd160 26 | */ 27 | hash: (input: Uint8Array) => Uint8Array; 28 | 29 | /** 30 | * Begin an incremental ripemd160 hashing computation. 31 | * 32 | * The returned raw state can be provided to `update` with additional input to 33 | * advance the computation. 34 | * 35 | * ## Example 36 | * ```ts 37 | * const state1 = ripemd160.init(); 38 | * const state2 = ripemd160.update(state1, new Uint8Array([1, 2, 3])); 39 | * const state3 = ripemd160.update(state2, new Uint8Array([4, 5, 6])); 40 | * const hash = ripemd160.final(state3); 41 | * ``` 42 | */ 43 | init: () => Uint8Array; 44 | 45 | /** 46 | * Add input to an incremental ripemd160 hashing computation. 47 | * 48 | * Returns a raw state that can again be passed to `update` with additional 49 | * input to continue the computation. 50 | * 51 | * When the computation has been updated with all input, pass the raw state to 52 | * `final` to finish and return a hash. 53 | * 54 | * @param rawState - a raw state returned by either `init` or `update` 55 | * @param input - a Uint8Array to be added to the ripemd160 computation 56 | */ 57 | update: (rawState: Uint8Array, input: Uint8Array) => Uint8Array; 58 | }; 59 | 60 | /** 61 | * The most performant way to instantiate ripemd160 functionality. To avoid 62 | * using Node.js or DOM-specific APIs, you can use {@link instantiateRipemd160}. 63 | * 64 | * @param webassemblyBytes - A buffer containing the ripemd160 binary. 65 | */ 66 | export const instantiateRipemd160Bytes = async ( 67 | webassemblyBytes: ArrayBuffer, 68 | ): Promise => { 69 | const wasm = await instantiateRustWasm( 70 | webassemblyBytes, 71 | './ripemd160', 72 | 'ripemd160', 73 | 'ripemd160_init', 74 | 'ripemd160_update', 75 | 'ripemd160_final', 76 | ); 77 | return { 78 | final: wasm.final, 79 | hash: wasm.hash, 80 | init: wasm.init, 81 | update: wasm.update, 82 | }; 83 | }; 84 | 85 | export const getEmbeddedRipemd160Binary = () => 86 | base64ToBin(ripemd160Base64Bytes).buffer; 87 | 88 | /** 89 | * An ultimately-portable (but slower) version of 90 | * {@link instantiateRipemd160Bytes} that does not require the consumer to 91 | * provide the ripemd160 binary buffer. 92 | */ 93 | export const instantiateRipemd160 = async (): Promise => 94 | instantiateRipemd160Bytes(getEmbeddedRipemd160Binary()); 95 | -------------------------------------------------------------------------------- /src/lib/crypto/sha1.bench.ts: -------------------------------------------------------------------------------- 1 | import type { Sha1 } from '../lib.js'; 2 | import { instantiateSha1 } from '../lib.js'; 3 | 4 | import { benchmarkHashingFunction } from './hash.bench.helper.js'; 5 | 6 | benchmarkHashingFunction('sha1', instantiateSha1(), 'sha1'); 7 | -------------------------------------------------------------------------------- /src/lib/crypto/sha1.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Sha1 } from '../lib.js'; 2 | import { 3 | getEmbeddedSha1Binary, 4 | instantiateSha1, 5 | instantiateSha1Bytes, 6 | } from '../lib.js'; 7 | 8 | import { testHashFunction } from './hash.spec.helper.js'; 9 | 10 | // prettier-ignore 11 | const abcHash = new Uint8Array([169, 153, 62, 54, 71, 6, 129, 106, 186, 62, 37, 113, 120, 80, 194, 108, 156, 208, 216, 157]); 12 | 13 | // prettier-ignore 14 | const testHash = new Uint8Array([169, 74, 143, 229, 204, 177, 155, 166, 28, 76, 8, 115, 211, 145, 233, 135, 152, 47, 187, 211]); 15 | 16 | // prettier-ignore 17 | const libauthHash = new Uint8Array([0, 53, 165, 162, 96, 82, 50, 137, 170, 76, 156, 212, 51, 123, 185, 71, 205, 18, 93, 14]); 18 | 19 | testHashFunction({ 20 | abcHash, 21 | getEmbeddedBinary: getEmbeddedSha1Binary, 22 | hashFunctionName: 'sha1', 23 | instantiate: instantiateSha1, 24 | instantiateBytes: instantiateSha1Bytes, 25 | libauthHash, 26 | nodeJsAlgorithm: 'sha1', 27 | testHash, 28 | }); 29 | -------------------------------------------------------------------------------- /src/lib/crypto/sha1.ts: -------------------------------------------------------------------------------- 1 | import type { HashFunction } from '../lib.js'; 2 | 3 | import { 4 | base64ToBin, 5 | instantiateRustWasm, 6 | sha1Base64Bytes, 7 | } from './dependencies.js'; 8 | 9 | export type Sha1 = HashFunction & { 10 | /** 11 | * Finish an incremental sha1 hashing computation. 12 | * 13 | * Returns the final hash. 14 | * 15 | * @param rawState - a raw state returned by `update`. 16 | */ 17 | final: (rawState: Uint8Array) => Uint8Array; 18 | 19 | /** 20 | * Returns the sha1 hash of the provided input. 21 | * 22 | * To incrementally construct a sha1 hash (e.g. for streaming), use `init`, 23 | * `update`, and `final`. 24 | * 25 | * @param input - a Uint8Array to be hashed using sha1 26 | */ 27 | hash: (input: Uint8Array) => Uint8Array; 28 | 29 | /** 30 | * Begin an incremental sha1 hashing computation. 31 | * 32 | * The returned raw state can be provided to `update` with additional input to 33 | * advance the computation. 34 | * 35 | * ## Example 36 | * ```ts 37 | * const state1 = sha1.init(); 38 | * const state2 = sha1.update(state1, new Uint8Array([1, 2, 3])); 39 | * const state3 = sha1.update(state2, new Uint8Array([4, 5, 6])); 40 | * const hash = sha1.final(state3); 41 | * ``` 42 | */ 43 | init: () => Uint8Array; 44 | 45 | /** 46 | * Add input to an incremental sha1 hashing computation. 47 | * 48 | * Returns a raw state that can again be passed to `update` with additional 49 | * input to continue the computation. 50 | * 51 | * When the computation has been updated with all input, pass the raw state to 52 | * `final` to finish and return a hash. 53 | * 54 | * @param rawState - a raw state returned by either `init` or `update` 55 | * @param input - a Uint8Array to be added to the sha1 computation 56 | */ 57 | update: (rawState: Uint8Array, input: Uint8Array) => Uint8Array; 58 | }; 59 | 60 | /** 61 | * The most performant way to instantiate sha1 functionality. To avoid 62 | * using Node.js or DOM-specific APIs, you can use {@link instantiateSha1}. 63 | * 64 | * @param webassemblyBytes - A buffer containing the sha1 binary. 65 | */ 66 | export const instantiateSha1Bytes = async ( 67 | webassemblyBytes: ArrayBuffer, 68 | ): Promise => { 69 | const wasm = await instantiateRustWasm( 70 | webassemblyBytes, 71 | './sha1', 72 | 'sha1', 73 | 'sha1_init', 74 | 'sha1_update', 75 | 'sha1_final', 76 | ); 77 | return { 78 | final: wasm.final, 79 | hash: wasm.hash, 80 | init: wasm.init, 81 | update: wasm.update, 82 | }; 83 | }; 84 | 85 | export const getEmbeddedSha1Binary = (): ArrayBuffer => 86 | base64ToBin(sha1Base64Bytes).buffer; 87 | 88 | /** 89 | * An ultimately-portable (but slower) version of {@link instantiateSha1Bytes} 90 | * that does not require the consumer to provide the sha1 binary buffer. 91 | */ 92 | export const instantiateSha1 = async (): Promise => 93 | instantiateSha1Bytes(getEmbeddedSha1Binary()); 94 | -------------------------------------------------------------------------------- /src/lib/crypto/sha256.bench.ts: -------------------------------------------------------------------------------- 1 | import type { Sha256 } from '../lib.js'; 2 | import { instantiateSha256 } from '../lib.js'; 3 | 4 | import { benchmarkHashingFunction } from './hash.bench.helper.js'; 5 | 6 | benchmarkHashingFunction('sha256', instantiateSha256(), 'sha256'); 7 | -------------------------------------------------------------------------------- /src/lib/crypto/sha256.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Sha256 } from '../lib.js'; 2 | import { 3 | getEmbeddedSha256Binary, 4 | instantiateSha256, 5 | instantiateSha256Bytes, 6 | } from '../lib.js'; 7 | 8 | import { testHashFunction } from './hash.spec.helper.js'; 9 | 10 | // prettier-ignore 11 | const abcHash = new Uint8Array([186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97, 163, 150, 23, 122, 156, 180, 16, 255, 97, 242, 0, 21, 173]); 12 | 13 | // prettier-ignore 14 | const testHash = new Uint8Array([159, 134, 208, 129, 136, 76, 125, 101, 154, 47, 234, 160, 197, 90, 208, 21, 163, 191, 79, 27, 43, 11, 130, 44, 209, 93, 108, 21, 176, 240, 10, 8]); 15 | 16 | // prettier-ignore 17 | const libauthHash = new Uint8Array([209, 125, 16, 114, 40, 162, 151, 83, 58, 228, 34, 240, 156, 140, 231, 64, 126, 178, 1, 161, 142, 172, 134, 169, 6, 119, 134, 200, 184, 30, 187, 120]); 18 | 19 | testHashFunction({ 20 | abcHash, 21 | getEmbeddedBinary: getEmbeddedSha256Binary, 22 | hashFunctionName: 'sha256', 23 | instantiate: instantiateSha256, 24 | instantiateBytes: instantiateSha256Bytes, 25 | libauthHash, 26 | nodeJsAlgorithm: 'sha256', 27 | testHash, 28 | }); 29 | -------------------------------------------------------------------------------- /src/lib/crypto/sha256.ts: -------------------------------------------------------------------------------- 1 | import type { HashFunction } from '../lib.js'; 2 | 3 | import { 4 | base64ToBin, 5 | instantiateRustWasm, 6 | sha256Base64Bytes, 7 | } from './dependencies.js'; 8 | 9 | export type Sha256 = HashFunction & { 10 | /** 11 | * Finish an incremental sha256 hashing computation. 12 | * 13 | * Returns the final hash. 14 | * 15 | * @param rawState - a raw state returned by `update`. 16 | */ 17 | final: (rawState: Uint8Array) => Uint8Array; 18 | 19 | /** 20 | * Returns the sha256 hash of the provided input. 21 | * 22 | * To incrementally construct a sha256 hash (e.g. for streaming), use `init`, 23 | * `update`, and `final`. 24 | * 25 | * @param input - a Uint8Array to be hashed using sha256 26 | */ 27 | hash: (input: Uint8Array) => Uint8Array; 28 | 29 | /** 30 | * Begin an incremental sha256 hashing computation. 31 | * 32 | * The returned raw state can be provided to `update` with additional input to 33 | * advance the computation. 34 | * 35 | * ## Example 36 | * ```ts 37 | * const state1 = sha256.init(); 38 | * const state2 = sha256.update(state1, new Uint8Array([1, 2, 3])); 39 | * const state3 = sha256.update(state2, new Uint8Array([4, 5, 6])); 40 | * const hash = sha256.final(state3); 41 | * ``` 42 | */ 43 | init: () => Uint8Array; 44 | 45 | /** 46 | * Add input to an incremental sha256 hashing computation. 47 | * 48 | * Returns a raw state which can again be passed to `update` with additional 49 | * input to continue the computation. 50 | * 51 | * When the computation has been updated with all input, pass the raw state to 52 | * `final` to finish and return a hash. 53 | * 54 | * @param rawState - a raw state returned by either `init` or `update` 55 | * @param input - a Uint8Array to be added to the sha256 computation 56 | */ 57 | update: (rawState: Uint8Array, input: Uint8Array) => Uint8Array; 58 | }; 59 | 60 | /** 61 | * The most performant way to instantiate sha256 functionality. To avoid 62 | * using Node.js or DOM-specific APIs, you can use {@link instantiateSha256}. 63 | * 64 | * @param webassemblyBytes - A buffer containing the sha256 binary. 65 | */ 66 | export const instantiateSha256Bytes = async ( 67 | webassemblyBytes: ArrayBuffer, 68 | ): Promise => { 69 | const wasm = await instantiateRustWasm( 70 | webassemblyBytes, 71 | './sha256', 72 | 'sha256', 73 | 'sha256_init', 74 | 'sha256_update', 75 | 'sha256_final', 76 | ); 77 | return { 78 | final: wasm.final, 79 | hash: wasm.hash, 80 | init: wasm.init, 81 | update: wasm.update, 82 | }; 83 | }; 84 | 85 | export const getEmbeddedSha256Binary = () => 86 | base64ToBin(sha256Base64Bytes).buffer; 87 | 88 | /** 89 | * An ultimately-portable (but possibly slower) version of 90 | * {@link instantiateSha256Bytes} which does not require the consumer to provide 91 | * the sha256 binary buffer. 92 | */ 93 | export const instantiateSha256 = async (): Promise => 94 | instantiateSha256Bytes(getEmbeddedSha256Binary()); 95 | -------------------------------------------------------------------------------- /src/lib/crypto/sha512.bench.ts: -------------------------------------------------------------------------------- 1 | import type { Sha512 } from '../lib.js'; 2 | import { instantiateSha512 } from '../lib.js'; 3 | 4 | import { benchmarkHashingFunction } from './hash.bench.helper.js'; 5 | 6 | benchmarkHashingFunction('sha512', instantiateSha512(), 'sha512'); 7 | -------------------------------------------------------------------------------- /src/lib/crypto/sha512.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Sha512 } from '../lib.js'; 2 | import { 3 | getEmbeddedSha512Binary, 4 | instantiateSha512, 5 | instantiateSha512Bytes, 6 | } from '../lib.js'; 7 | 8 | import { testHashFunction } from './hash.spec.helper.js'; 9 | 10 | // prettier-ignore 11 | const abcHash = new Uint8Array([221, 175, 53, 161, 147, 97, 122, 186, 204, 65, 115, 73, 174, 32, 65, 49, 18, 230, 250, 78, 137, 169, 126, 162, 10, 158, 238, 230, 75, 85, 211, 154, 33, 146, 153, 42, 39, 79, 193, 168, 54, 186, 60, 35, 163, 254, 235, 189, 69, 77, 68, 35, 100, 60, 232, 14, 42, 154, 201, 79, 165, 76, 164, 159]); 12 | 13 | // prettier-ignore 14 | const testHash = new Uint8Array([238, 38, 176, 221, 74, 247, 231, 73, 170, 26, 142, 227, 193, 10, 233, 146, 63, 97, 137, 128, 119, 46, 71, 63, 136, 25, 165, 212, 148, 14, 13, 178, 122, 193, 133, 248, 160, 225, 213, 248, 79, 136, 188, 136, 127, 214, 123, 20, 55, 50, 195, 4, 204, 95, 169, 173, 142, 111, 87, 245, 0, 40, 168, 255]); 15 | 16 | // prettier-ignore 17 | const libauthHash = new Uint8Array([27, 119, 76, 47, 15, 63, 203, 10, 157, 198, 236, 115, 55, 254, 4, 166, 127, 194, 140, 208, 81, 198, 141, 31, 81, 27, 240, 215, 32, 131, 13, 206, 240, 192, 196, 5, 189, 226, 121, 119, 173, 141, 227, 101, 2, 146, 59, 6, 120, 5, 24, 222, 22, 230, 116, 153, 116, 205, 56, 40, 138, 26, 29, 230]); 18 | 19 | testHashFunction({ 20 | abcHash, 21 | getEmbeddedBinary: getEmbeddedSha512Binary, 22 | hashFunctionName: 'sha512', 23 | instantiate: instantiateSha512, 24 | instantiateBytes: instantiateSha512Bytes, 25 | libauthHash, 26 | nodeJsAlgorithm: 'sha512', 27 | testHash, 28 | }); 29 | -------------------------------------------------------------------------------- /src/lib/crypto/sha512.ts: -------------------------------------------------------------------------------- 1 | import type { HashFunction } from '../lib.js'; 2 | 3 | import { 4 | base64ToBin, 5 | instantiateRustWasm, 6 | sha512Base64Bytes, 7 | } from './dependencies.js'; 8 | 9 | export type Sha512 = HashFunction & { 10 | /** 11 | * Finish an incremental sha512 hashing computation. 12 | * 13 | * Returns the final hash. 14 | * 15 | * @param rawState - a raw state returned by `update`. 16 | */ 17 | final: (rawState: Uint8Array) => Uint8Array; 18 | 19 | /** 20 | * Returns the sha512 hash of the provided input. 21 | * 22 | * To incrementally construct a sha512 hash (e.g. for streaming), use `init`, 23 | * `update`, and `final`. 24 | * 25 | * @param input - a Uint8Array to be hashed using sha512 26 | */ 27 | hash: (input: Uint8Array) => Uint8Array; 28 | 29 | /** 30 | * Begin an incremental sha512 hashing computation. 31 | * 32 | * The returned raw state can be provided to `update` with additional input to 33 | * advance the computation. 34 | * 35 | * ## Example 36 | * ```ts 37 | * const state1 = sha512.init(); 38 | * const state2 = sha512.update(state1, new Uint8Array([1, 2, 3])); 39 | * const state3 = sha512.update(state2, new Uint8Array([4, 5, 6])); 40 | * const hash = sha512.final(state3); 41 | * ``` 42 | */ 43 | init: () => Uint8Array; 44 | 45 | /** 46 | * Add input to an incremental sha512 hashing computation. 47 | * 48 | * Returns a raw state that can again be passed to `update` with additional 49 | * input to continue the computation. 50 | * 51 | * When the computation has been updated with all input, pass the raw state to 52 | * `final` to finish and return a hash. 53 | * 54 | * @param rawState - a raw state returned by either `init` or `update` 55 | * @param input - a Uint8Array to be added to the sha512 computation 56 | */ 57 | update: (rawState: Uint8Array, input: Uint8Array) => Uint8Array; 58 | }; 59 | 60 | /** 61 | * The most performant way to instantiate sha512 functionality. To avoid 62 | * using Node.js or DOM-specific APIs, you can use {@link instantiateSha512}. 63 | * 64 | * @param webassemblyBytes - A buffer containing the sha512 binary. 65 | */ 66 | export const instantiateSha512Bytes = async ( 67 | webassemblyBytes: ArrayBuffer, 68 | ): Promise => { 69 | const wasm = await instantiateRustWasm( 70 | webassemblyBytes, 71 | './sha512', 72 | 'sha512', 73 | 'sha512_init', 74 | 'sha512_update', 75 | 'sha512_final', 76 | ); 77 | return { 78 | final: wasm.final, 79 | hash: wasm.hash, 80 | init: wasm.init, 81 | update: wasm.update, 82 | }; 83 | }; 84 | 85 | export const getEmbeddedSha512Binary = () => 86 | base64ToBin(sha512Base64Bytes).buffer; 87 | 88 | /** 89 | * An ultimately-portable (but slower) version of {@link instantiateSha512Bytes} 90 | * that does not require the consumer to provide the sha512 binary buffer. 91 | */ 92 | export const instantiateSha512 = async (): Promise => 93 | instantiateSha512Bytes(getEmbeddedSha512Binary()); 94 | -------------------------------------------------------------------------------- /src/lib/engine/engine.ts: -------------------------------------------------------------------------------- 1 | export * from './types/bcmr-types.js'; 2 | // export * from './types/draft-transaction-workspace-types.js'; 3 | export * from './types/template-types.js'; 4 | /* 5 | * export * from './types/wallet-types.js'; 6 | * export * from './types/wallet-activity-types.js'; 7 | * export * from './types/wallet-set-types.js'; 8 | */ 9 | -------------------------------------------------------------------------------- /src/lib/format/base64.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { base64ToBin, binToBase64, isBase64 } from '../lib.js'; 4 | 5 | test('isBase64', (t) => { 6 | t.deepEqual(isBase64('YWJj'), true); 7 | t.deepEqual(isBase64('YWJjZA=='), true); 8 | t.deepEqual(isBase64('YWJ&'), false); 9 | t.deepEqual(isBase64('YWJ'), false); 10 | t.deepEqual(isBase64('YW'), false); 11 | t.deepEqual(isBase64('Y'), false); 12 | }); 13 | 14 | test('base64ToBin works as expected', (t) => { 15 | const abc = new Uint8Array([97, 98, 99]); 16 | const abcd = new Uint8Array([97, 98, 99, 100]); 17 | const abcde = new Uint8Array([97, 98, 99, 100, 101]); 18 | t.deepEqual(base64ToBin('YWJj'), abc); 19 | t.deepEqual(base64ToBin('YWJjZA=='), abcd); 20 | t.deepEqual(base64ToBin('YWJjZGU='), abcde); 21 | }); 22 | 23 | test('binToBase64 works as expected', (t) => { 24 | const abc = 'YWJj'; 25 | const abcd = 'YWJjZA=='; 26 | const abcde = 'YWJjZGU='; 27 | t.deepEqual(binToBase64(Uint8Array.from([97, 98, 99])), abc); 28 | t.deepEqual(binToBase64(Uint8Array.from([97, 98, 99, 100])), abcd); 29 | t.deepEqual(binToBase64(Uint8Array.from([97, 98, 99, 100, 101])), abcde); 30 | }); 31 | -------------------------------------------------------------------------------- /src/lib/format/bin-string.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { binStringToBin, binToBinString, isBinString } from '../lib.js'; 4 | 5 | import { fc, testProp } from '@fast-check/ava'; 6 | 7 | test('isBinString', (t) => { 8 | t.deepEqual(isBinString('0'), false); 9 | t.deepEqual(isBinString('01'), false); 10 | t.deepEqual(isBinString('00000000'), true); 11 | t.deepEqual(isBinString('0000000 '), false); 12 | t.deepEqual(isBinString('00000001'), true); 13 | t.deepEqual(isBinString('00000002'), false); 14 | t.deepEqual(isBinString('0000000100000001'), true); 15 | t.deepEqual(isBinString('000000010000000100000001'), true); 16 | }); 17 | 18 | test('binStringToBin', (t) => { 19 | t.deepEqual(binStringToBin('0010101001100100'), Uint8Array.from([42, 100])); 20 | }); 21 | 22 | test('binToBinString', (t) => { 23 | t.deepEqual(binToBinString(Uint8Array.from([42, 100])), '0010101001100100'); 24 | }); 25 | 26 | testProp( 27 | '[fast-check] binStringToBin <-> binToBinString', 28 | [fc.uint8Array({ maxLength: 100, minLength: 0 })], 29 | (t, input) => 30 | t.deepEqual( 31 | binToBinString(binStringToBin(binToBinString(input))), 32 | binToBinString(input), 33 | ), 34 | ); 35 | -------------------------------------------------------------------------------- /src/lib/format/bin-string.ts: -------------------------------------------------------------------------------- 1 | import { splitEvery } from './hex.js'; 2 | 3 | const binaryByteWidth = 8; 4 | const binary = 2; 5 | 6 | /** 7 | * Decode a binary-encoded string into a Uint8Array. 8 | * 9 | * E.g.: `binStringToBin('0010101001100100')` → `new Uint8Array([42, 100])` 10 | * 11 | * Note, this method always completes. If `binaryDigits` is not divisible by 8, 12 | * the final byte will be parsed as if it were prepended with `0`s (e.g. `1` 13 | * is interpreted as `00000001`). If `binaryDigits` is potentially malformed, 14 | * check it with `isBinString` before calling this method. 15 | * 16 | * For the reverse, see {@link binToBinString}. 17 | * 18 | * @param binaryDigits - a string of `0`s and `1`s with a length divisible by 8 19 | */ 20 | export const binStringToBin = (binaryDigits: string) => 21 | Uint8Array.from( 22 | splitEvery(binaryDigits, binaryByteWidth).map((byteString) => 23 | parseInt(byteString, binary), 24 | ), 25 | ); 26 | 27 | /** 28 | * Encode a Uint8Array into a binary-encoded string. 29 | * 30 | * E.g.: `binToBinString(Uint8Array.from([42, 100]))` → `'0010101001100100'` 31 | * 32 | * For the reverse, see {@link binStringToBin}. 33 | * 34 | * @param bytes - a Uint8Array to encode 35 | */ 36 | export const binToBinString = (bytes: Uint8Array) => 37 | bytes.reduce( 38 | (str, byte) => str + byte.toString(binary).padStart(binaryByteWidth, '0'), 39 | '', 40 | ); 41 | 42 | /** 43 | * For use before {@link binStringToBin}. Returns true if the provided string is 44 | * a valid binary string (length is divisible by 8 and only uses the characters 45 | * `0` and `1`). 46 | * @param maybeBinString - a string to test 47 | */ 48 | export const isBinString = (maybeBinString: string) => 49 | maybeBinString.length % binaryByteWidth === 0 && 50 | !/[^01]/u.test(maybeBinString); 51 | -------------------------------------------------------------------------------- /src/lib/format/error.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | assertSuccess, 5 | binToHex, 6 | decodeCashAddress, 7 | formatError, 8 | unknownValue, 9 | } from '../lib.js'; 10 | 11 | const enum TestConstEnum { 12 | one = 'TestConstEnum one.', 13 | } 14 | enum TestEnum { 15 | one = 'TestEnum one.', 16 | } 17 | 18 | test('formatError', (t) => { 19 | t.deepEqual( 20 | formatError(TestConstEnum.one, 'More text.'), 21 | 'TestConstEnum one. More text.', 22 | ); 23 | t.deepEqual(formatError(TestEnum.one, 'More.'), 'TestEnum one. More.'); 24 | t.deepEqual(formatError('Anything.'), 'Anything.'); 25 | t.deepEqual(formatError('Anything.', 'Details.'), 'Anything. Details.'); 26 | }); 27 | 28 | test('unknownValue', (t) => { 29 | const val = 'something' as never; 30 | t.throws( 31 | () => { 32 | unknownValue(val); 33 | }, 34 | { 35 | message: 36 | 'Received an unknown value; this should have been caught by TypeScript - are your types correct? something', 37 | }, 38 | ); 39 | }); 40 | 41 | test('assertSuccess', (t) => { 42 | const resultError = 'error' as Uint8Array | string; 43 | const resultSuccess = Uint8Array.of(0) as Uint8Array | string; 44 | t.throws( 45 | () => { 46 | assertSuccess(resultError); 47 | }, 48 | { 49 | message: 'Expected a successful result, but encountered an error: error', 50 | }, 51 | ); 52 | const unwrapped: Uint8Array = assertSuccess(resultSuccess); 53 | t.deepEqual(unwrapped, Uint8Array.of(0)); 54 | t.throws( 55 | () => { 56 | assertSuccess(resultError, 'Custom prefix: '); 57 | }, 58 | { 59 | message: 'Custom prefix: error', 60 | }, 61 | ); 62 | }); 63 | 64 | test('assertSuccess (tsdoc example)', (t) => { 65 | const address = 'bitcoincash:zq2azmyyv6dtgczexyalqar70q036yund5j2mspghf'; 66 | const decoded = decodeCashAddress(address); 67 | const tokenAddress = assertSuccess(decoded); 68 | t.deepEqual( 69 | binToHex(tokenAddress.payload), 70 | '15d16c84669ab46059313bf0747e781f1d13936d', 71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /src/lib/format/fixtures/base58_encode_decode.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["", ""], 3 | ["61", "2g"], 4 | ["626262", "a3gV"], 5 | ["636363", "aPEr"], 6 | ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], 7 | [ 8 | "00eb15231dfceb60925886b67d065299925915aeb172c06647", 9 | "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L" 10 | ], 11 | ["516b6fcd0f", "ABnLTmg"], 12 | ["bf4f89001e670274dd", "3SEo3LWLoPntC"], 13 | ["572e4794", "3EFU7m"], 14 | ["ecac89cad93923c02321", "EJDM8drfXA6uyA"], 15 | ["10c8511e", "Rt5zm"], 16 | ["00000000000000000000", "1111111111"], 17 | [ 18 | "000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", 19 | "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 20 | ], 21 | [ 22 | "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 23 | "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY" 24 | ] 25 | ] 26 | -------------------------------------------------------------------------------- /src/lib/format/format.ts: -------------------------------------------------------------------------------- 1 | export * from './base-convert.js'; 2 | export * from './base64.js'; 3 | export * from './bin-string.js'; 4 | export * from './error.js'; 5 | export * from './hex.js'; 6 | export * from './log.js'; 7 | export * from './number.js'; 8 | export * from './read.js'; 9 | export * from './time.js'; 10 | export * from './type-utils.js'; 11 | export * from './utf8.js'; 12 | -------------------------------------------------------------------------------- /src/lib/format/hex.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | binToHex, 5 | hexToBin, 6 | isHex, 7 | range, 8 | splitEvery, 9 | swapEndianness, 10 | } from '../lib.js'; 11 | 12 | import { fc, testProp } from '@fast-check/ava'; 13 | 14 | test('range', (t) => { 15 | t.deepEqual(range(3), [0, 1, 2]); 16 | t.deepEqual(range(3, 1), [1, 2, 3]); 17 | }); 18 | 19 | test('splitEvery', (t) => { 20 | t.deepEqual(splitEvery('abcd', 2), ['ab', 'cd']); 21 | t.deepEqual(splitEvery('abcde', 2), ['ab', 'cd', 'e']); 22 | }); 23 | 24 | test('isHex', (t) => { 25 | t.deepEqual(isHex('0001022a646566ff'), true); 26 | t.deepEqual(isHex('0001022A646566Ff'), true); 27 | t.deepEqual(isHex('0001022A646566FF'), true); 28 | t.deepEqual(isHex('0001022A646566F'), false); 29 | t.deepEqual(isHex('0001022A646566FG'), false); 30 | }); 31 | 32 | test('hexToBin', (t) => { 33 | t.deepEqual( 34 | hexToBin('0001022a646566ff'), 35 | Uint8Array.from([0, 1, 2, 42, 100, 101, 102, 255]), 36 | ); 37 | t.deepEqual( 38 | hexToBin('0001022A646566FF'), 39 | Uint8Array.from([0, 1, 2, 42, 100, 101, 102, 255]), 40 | ); 41 | }); 42 | 43 | test('binToHex', (t) => { 44 | t.deepEqual( 45 | binToHex(Uint8Array.from([0, 1, 2, 42, 100, 101, 102, 255])), 46 | '0001022a646566ff', 47 | ); 48 | }); 49 | 50 | testProp( 51 | '[fast-check] hexToBin <-> binToHex', 52 | [fc.uint8Array({ maxLength: 100, minLength: 0 })], 53 | (t, input) => 54 | t.deepEqual(binToHex(hexToBin(binToHex(input))), binToHex(input)), 55 | ); 56 | 57 | test('swapEndianness', (t) => { 58 | t.deepEqual(swapEndianness('0001022a646566ff'), 'ff6665642a020100'); 59 | }); 60 | -------------------------------------------------------------------------------- /src/lib/format/log.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { sortObjectKeys, stringify, stringifyTestVector } from '../lib.js'; 4 | 5 | test('stringify', (t) => { 6 | t.deepEqual(stringify(0n), '""'); 7 | t.deepEqual(stringify({ a: 0n }), '{\n "a": ""\n}'); 8 | t.deepEqual(stringify(Uint8Array.of(32, 32)), '""'); 9 | t.deepEqual( 10 | stringify({ b: Uint8Array.of(32, 32) }), 11 | '{\n "b": ""\n}', 12 | ); 13 | t.deepEqual( 14 | stringify((x: number) => x * 2), 15 | '" x * 2>"', 16 | ); 17 | t.deepEqual( 18 | stringify({ c: (x: number) => x * 2 }), 19 | '{\n "c": " x * 2>"\n}', 20 | ); 21 | t.deepEqual(stringify(Symbol('A')), '""'); 22 | t.deepEqual( 23 | stringify({ d: Symbol('A') }), 24 | '{\n "d": ""\n}', 25 | ); 26 | }); 27 | 28 | test('sortObjectKeys', (t) => { 29 | t.deepEqual(sortObjectKeys(0n), 0n); 30 | t.deepEqual(sortObjectKeys(Uint8Array.of(32, 32)), Uint8Array.of(32, 32)); 31 | t.deepEqual( 32 | sortObjectKeys({ 33 | b: { ...{ c: 1 }, a: 2, b: null }, 34 | ...{ a: Uint8Array.of(2), c: Uint8Array.of(3) }, 35 | }), 36 | { a: Uint8Array.of(2), b: { a: 2, b: null, c: 1 }, c: Uint8Array.of(3) }, 37 | ); 38 | const func = (x: number) => x * 2; 39 | t.deepEqual(sortObjectKeys(func), func); 40 | const sym = Symbol('A'); 41 | t.deepEqual(sortObjectKeys(sym), sym); 42 | t.deepEqual( 43 | sortObjectKeys([ 44 | 3, 45 | 2, 46 | { b: Uint8Array.of(1), ...{ a: Uint8Array.of(2), c: Uint8Array.of(3) } }, 47 | 1, 48 | ]), 49 | [ 50 | 3, 51 | 2, 52 | { a: Uint8Array.of(2), b: Uint8Array.of(1), c: Uint8Array.of(3) }, 53 | 1, 54 | ], 55 | ); 56 | }); 57 | 58 | test('stringifyTestVector', (t) => { 59 | const one = stringifyTestVector({ 60 | ...{ b: [1, 2, { a: 1, ...{ c: Uint8Array.of(2) }, b: 3 }] }, 61 | a: { a: 1, ...{ c: 2n }, b: 3 }, 62 | }); 63 | t.deepEqual( 64 | one, 65 | `{ 66 | "a": { 67 | "a": 1, 68 | "b": 3, 69 | "c": 2n 70 | }, 71 | "b": [ 72 | 1, 73 | 2, 74 | { 75 | "a": 1, 76 | "b": 3, 77 | "c": hexToBin('02') 78 | } 79 | ] 80 | }`, 81 | one, 82 | ); 83 | }); 84 | -------------------------------------------------------------------------------- /src/lib/format/number.bench.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-let, @typescript-eslint/init-declarations */ 2 | 3 | import { randomBytes } from 'crypto'; 4 | 5 | import test from 'ava'; 6 | 7 | import { 8 | bigIntToBinUint64LE, 9 | bigIntToBinUint64LEClamped, 10 | binToBigIntUint64LE, 11 | binToBigIntUintBE, 12 | binToHex, 13 | binToNumberUint16LE, 14 | binToNumberUint32LE, 15 | numberToBinUint16LE, 16 | numberToBinUint16LEClamped, 17 | numberToBinUint32LE, 18 | numberToBinUint32LEClamped, 19 | } from '../lib.js'; 20 | 21 | import suite from 'chuhai'; 22 | 23 | test(`node: binToBigIntUintBE vs. binToHex -> BigInt()`, async (t) => { 24 | await suite(t.title, (s) => { 25 | let sourceBin: Uint8Array; 26 | let num: bigint; 27 | let result: bigint; 28 | 29 | const nextCycle = () => { 30 | const uint256Length = 32; 31 | sourceBin = Uint8Array.from(randomBytes(uint256Length)); 32 | num = binToBigIntUintBE(sourceBin); 33 | }; 34 | nextCycle(); 35 | 36 | s.bench('binToBigIntUintBE', () => { 37 | result = binToBigIntUintBE(sourceBin); 38 | }); 39 | s.bench('binToHex -> BigInt()', () => { 40 | result = BigInt(`0x${binToHex(sourceBin)}`); 41 | }); 42 | 43 | s.cycle(() => { 44 | t.deepEqual(result, num); 45 | nextCycle(); 46 | }); 47 | }); 48 | }); 49 | 50 | test(`node: numberToBinUint16LE vs. numberToBinUint16LEClamped`, async (t) => { 51 | await suite(t.title, (s) => { 52 | let expectedBin: Uint8Array; 53 | let num: number; 54 | let resultBin: Uint8Array; 55 | 56 | const nextCycle = () => { 57 | const uint16Length = 2; 58 | expectedBin = Uint8Array.from(randomBytes(uint16Length)); 59 | num = binToNumberUint16LE(expectedBin); 60 | }; 61 | nextCycle(); 62 | 63 | s.bench('numberToBinUint16LE', () => { 64 | resultBin = numberToBinUint16LE(num); 65 | }); 66 | s.bench('numberToBinUint16LEClamped', () => { 67 | resultBin = numberToBinUint16LEClamped(num); 68 | }); 69 | 70 | s.cycle(() => { 71 | t.deepEqual(resultBin, expectedBin); 72 | nextCycle(); 73 | }); 74 | }); 75 | }); 76 | 77 | test(`node: numberToBinUint32LE vs. numberToBinUint32LEClamped`, async (t) => { 78 | await suite(t.title, (s) => { 79 | let expectedBin: Uint8Array; 80 | let num: number; 81 | let resultBin: Uint8Array; 82 | 83 | const nextCycle = () => { 84 | const uint32Length = 4; 85 | expectedBin = Uint8Array.from(randomBytes(uint32Length)); 86 | num = binToNumberUint32LE(expectedBin); 87 | }; 88 | nextCycle(); 89 | 90 | s.bench('numberToBinUint32LE', () => { 91 | resultBin = numberToBinUint32LE(num); 92 | }); 93 | s.bench('numberToBinUint32LEClamped', () => { 94 | resultBin = numberToBinUint32LEClamped(num); 95 | }); 96 | 97 | s.cycle(() => { 98 | t.deepEqual(resultBin, expectedBin); 99 | nextCycle(); 100 | }); 101 | }); 102 | }); 103 | 104 | test(`node: bigIntToBinUint64LE vs. bigIntToBinUint64LEClamped`, async (t) => { 105 | await suite(t.title, (s) => { 106 | let expectedBin: Uint8Array; 107 | let num: bigint; 108 | let resultBin: Uint8Array; 109 | 110 | const nextCycle = () => { 111 | const uint64Length = 8; 112 | expectedBin = Uint8Array.from(randomBytes(uint64Length)); 113 | num = binToBigIntUint64LE(expectedBin); 114 | }; 115 | nextCycle(); 116 | 117 | s.bench('bigIntToBinUint64LE', () => { 118 | resultBin = bigIntToBinUint64LE(num); 119 | }); 120 | s.bench('bigIntToBinUint64LEClamped', () => { 121 | resultBin = bigIntToBinUint64LEClamped(num); 122 | }); 123 | 124 | s.cycle(() => { 125 | t.deepEqual(resultBin, expectedBin); 126 | nextCycle(); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /src/lib/format/read.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { readItemCount, readMultiple } from '../lib.js'; 4 | import { readBytes } from '../message/read-components.js'; 5 | 6 | test('readMultiple', (t) => { 7 | t.deepEqual( 8 | readMultiple({ bin: Uint8Array.from([0, 1, 2, 3, 4]), index: 0 }, [ 9 | readBytes(0), 10 | readBytes(1), 11 | readBytes(3), 12 | ]), 13 | { 14 | position: { bin: Uint8Array.from([0, 1, 2, 3, 4]), index: 4 }, 15 | result: [ 16 | Uint8Array.from([]), 17 | Uint8Array.from([0]), 18 | Uint8Array.from([1, 2, 3]), 19 | ], 20 | }, 21 | ); 22 | t.deepEqual( 23 | readMultiple({ bin: Uint8Array.from([0, 1, 2, 3, 4]), index: 0 }, [ 24 | readBytes(0), 25 | readBytes(1), 26 | readBytes(3), 27 | readBytes(2), 28 | ]), 29 | 'Error reading bytes: insufficient length. Bytes requested: 2; remaining bytes: 1', 30 | ); 31 | }); 32 | 33 | test('readItemCount', (t) => { 34 | t.deepEqual( 35 | readItemCount( 36 | { bin: Uint8Array.from([0, 1, 2, 3, 4]), index: 0 }, 37 | readBytes(2), 38 | ), 39 | { 40 | position: { bin: Uint8Array.from([0, 1, 2, 3, 4]), index: 1 }, 41 | result: [], 42 | }, 43 | ); 44 | t.deepEqual( 45 | readItemCount( 46 | { bin: Uint8Array.from([1, 1, 2, 3, 4]), index: 0 }, 47 | readBytes(2), 48 | ), 49 | { 50 | position: { bin: Uint8Array.from([1, 1, 2, 3, 4]), index: 3 }, 51 | result: [Uint8Array.from([1, 2])], 52 | }, 53 | ); 54 | t.deepEqual( 55 | readItemCount( 56 | { bin: Uint8Array.from([2, 1, 2, 3, 4]), index: 0 }, 57 | readBytes(2), 58 | ), 59 | { 60 | position: { bin: Uint8Array.from([2, 1, 2, 3, 4]), index: 5 }, 61 | result: [Uint8Array.from([1, 2]), Uint8Array.from([3, 4])], 62 | }, 63 | ); 64 | t.deepEqual( 65 | readItemCount({ bin: Uint8Array.from([0xfd, 1]), index: 0 }, readBytes(2)), 66 | 'Error reading item count. Error reading CompactUint: insufficient bytes. CompactUint prefix 253 requires at least 3 bytes. Remaining bytes: 2', 67 | ); 68 | t.deepEqual( 69 | readItemCount({ bin: Uint8Array.from([1, 1]), index: 0 }, readBytes(2)), 70 | 'Error reading item. Error reading bytes: insufficient length. Bytes requested: 2; remaining bytes: 1', 71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /src/lib/format/time.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | dateToLocktime, 5 | dateToLocktimeBin, 6 | decodeLocktime, 7 | hexToBin, 8 | LocktimeError, 9 | locktimeToDate, 10 | maximumLocktimeDate, 11 | maximumLocktimeTimestamp, 12 | minimumLocktimeDate, 13 | minimumLocktimeTimestamp, 14 | } from '../lib.js'; 15 | 16 | import { fc, testProp } from '@fast-check/ava'; 17 | 18 | test('dateToLocktime', (t) => { 19 | t.deepEqual(dateToLocktime(new Date('2019-10-13')), 1570924800); 20 | t.deepEqual( 21 | dateToLocktime(new Date('2107-01-01')), 22 | LocktimeError.dateOutOfRange, 23 | ); 24 | }); 25 | 26 | test('dateToLocktimeBin', (t) => { 27 | t.deepEqual(dateToLocktimeBin(new Date('2019-10-13')), hexToBin('0069a25d')); 28 | t.deepEqual( 29 | dateToLocktimeBin(new Date('2107-01-01')), 30 | LocktimeError.dateOutOfRange, 31 | ); 32 | }); 33 | 34 | test('parseLockTime', (t) => { 35 | t.deepEqual(decodeLocktime(hexToBin('0069a25d')), new Date('2019-10-13')); 36 | t.deepEqual(decodeLocktime(hexToBin('d090371c')), 473403600); 37 | t.deepEqual(decodeLocktime(hexToBin('')), LocktimeError.incorrectLength); 38 | t.deepEqual(decodeLocktime(hexToBin('00')), LocktimeError.incorrectLength); 39 | t.deepEqual( 40 | decodeLocktime(hexToBin('0000000000')), 41 | LocktimeError.incorrectLength, 42 | ); 43 | }); 44 | 45 | testProp( 46 | '[fast-check] dateToLocktime <-> locktimeToDate', 47 | [ 48 | fc.integer({ 49 | max: maximumLocktimeTimestamp, 50 | min: minimumLocktimeTimestamp, 51 | }), 52 | ], 53 | (t, timestamp) => 54 | t.deepEqual(dateToLocktime(locktimeToDate(timestamp) as Date), timestamp), 55 | ); 56 | 57 | testProp( 58 | '[fast-check] dateToLocktimeBin <-> decodeLocktime', 59 | [fc.date({ max: maximumLocktimeDate, min: minimumLocktimeDate })], 60 | (t, date) => { 61 | const withSecondResolution = new Date( 62 | Math.round(date.getTime() / 1000) * 1000, 63 | ); 64 | t.deepEqual( 65 | ( 66 | decodeLocktime( 67 | dateToLocktimeBin(withSecondResolution) as Uint8Array, 68 | ) as Date 69 | ).getTime(), 70 | withSecondResolution.getTime(), 71 | ); 72 | }, 73 | ); 74 | -------------------------------------------------------------------------------- /src/lib/format/type-utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An implementation of the built-in `Partial` utility that allows explicit 3 | * `undefined` values when 4 | * [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) 5 | * is enabled. 6 | */ 7 | export type PartialExactOptional = { 8 | [P in keyof T]?: T[P] | undefined; 9 | }; 10 | 11 | type FunctionComparisonEqualsWrapped = T extends ( // eslint-disable-next-line @typescript-eslint/ban-types 12 | T extends Readonly<{}> ? infer R & Readonly<{}> : infer R 13 | ) 14 | ? { 15 | [P in keyof R]: R[P]; 16 | } 17 | : never; 18 | type FunctionComparisonEquals = (< 19 | T, 20 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 21 | >() => T extends FunctionComparisonEqualsWrapped ? 1 : 2) extends < 22 | T, 23 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 24 | >() => T extends FunctionComparisonEqualsWrapped ? 1 : 2 25 | ? true 26 | : false; 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 29 | type IsAny = FunctionComparisonEquals; 30 | // eslint-disable-next-line functional/no-mixed-types 31 | type InvariantComparisonEqualsWrapped = { 32 | value: T; 33 | setValue: (value: T) => never; 34 | }; 35 | type InvariantComparisonEquals = 36 | InvariantComparisonEqualsWrapped extends InvariantComparisonEqualsWrapped 37 | ? IsAny extends true 38 | ? IsAny | IsAny extends true 39 | ? true 40 | : false 41 | : true 42 | : false; 43 | 44 | // Derived from https://github.com/DetachHead/ts-helpers 45 | type Equals = InvariantComparisonEquals< 46 | Expected, 47 | Actual 48 | > extends true 49 | ? FunctionComparisonEquals 50 | : false; 51 | 52 | export type AssertTypesEqual = Equals extends true 53 | ? true 54 | : never; 55 | -------------------------------------------------------------------------------- /src/lib/format/utf8.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | binToUtf8, 5 | hexToBin, 6 | length, 7 | lossyNormalize, 8 | segment, 9 | utf8ToBin, 10 | } from '../lib.js'; 11 | 12 | import { testProp } from '@fast-check/ava'; 13 | import fc from 'fast-check'; 14 | 15 | const maxUint8Number = 255; 16 | const fcUint8Array = (minLength: number, maxLength: number) => 17 | fc 18 | .array(fc.integer({ max: maxUint8Number, min: 0 }), { 19 | maxLength, 20 | minLength, 21 | }) 22 | .map((a) => Uint8Array.from(a)); 23 | 24 | test('utf8ToBin', (t) => { 25 | t.deepEqual(utf8ToBin('👍'), hexToBin('f09f918d')); 26 | }); 27 | 28 | test('binToUtf8', (t) => { 29 | t.deepEqual(binToUtf8(hexToBin('f09f918d')), '👍'); 30 | }); 31 | 32 | const testBinLength = 100; 33 | testProp( 34 | '[fast-check] utf8ToBin <-> binToUtf8', 35 | [fcUint8Array(0, testBinLength)], 36 | (t, input) => 37 | t.deepEqual(binToUtf8(utf8ToBin(binToUtf8(input))), binToUtf8(input)), 38 | ); 39 | 40 | const nonNormal = 'fit🚀👫👨‍👩‍👧‍👦'; 41 | test('lossyNormalize', (t) => { 42 | t.deepEqual(lossyNormalize(nonNormal), 'fit🚀👫👨‍👩‍👧‍👦'); 43 | }); 44 | 45 | test('segment', (t) => { 46 | t.deepEqual( 47 | [...nonNormal], 48 | ['fi', 't', '🚀', '👫', '👨', '‍', '👩', '‍', '👧', '‍', '👦'], 49 | ); 50 | t.deepEqual(segment(nonNormal), ['fi', 't', '🚀', '👫', '👨‍👩‍👧‍👦']); 51 | }); 52 | 53 | test('length', (t) => { 54 | t.deepEqual(nonNormal.length, 17); 55 | t.deepEqual(length(nonNormal), 5); 56 | }); 57 | -------------------------------------------------------------------------------- /src/lib/format/utf8.ts: -------------------------------------------------------------------------------- 1 | const utf8Encoder = new TextEncoder(); 2 | 3 | /** 4 | * Interpret a string as UTF-8 and encode it as a Uint8Array. 5 | * 6 | * For the reverse, see {@link binToUtf8}. 7 | * 8 | * @param utf8 - the string to encode 9 | */ 10 | export const utf8ToBin = (utf8: string) => utf8Encoder.encode(utf8); 11 | 12 | const utf8Decoder = new TextDecoder(); 13 | /** 14 | * Decode a Uint8Array as a UTF-8 string. 15 | * 16 | * For the reverse, see {@link utf8ToBin}. 17 | * 18 | * @param bytes - the Uint8Array to decode 19 | */ 20 | export const binToUtf8 = (bytes: Uint8Array) => utf8Decoder.decode(bytes); 21 | 22 | /** 23 | * Normalize a string using Unicode Normalization Form KC (NFKC): compatibility 24 | * decomposition, followed by canonical composition. NFKC is the preferred form 25 | * for applications in which disambiguation between characters is critical. In 26 | * Libauth, all message formats designed for transmission between trust centers 27 | * are NFKC-normalized to hinder exploits in which lookalike characters are used 28 | * to deceive counterparties. 29 | * 30 | * E.g.: 31 | * ``` 32 | * console.log(lossyNormalize('fit🚀👫👨‍👩‍👧‍👦')); // 'fit🚀👫👨‍👩‍👧‍👦' 33 | * ``` 34 | */ 35 | export const lossyNormalize = (utf8: string) => utf8.normalize('NFKC'); 36 | 37 | /** 38 | * Return the user-perceived character segments of the given string, e.g.: 39 | * 40 | * ```js 41 | * const test = 'fit🚀👫👨‍👩‍👧‍👦'; 42 | * console.log([...test]); // '["fi","t","🚀","👫","👨","‍","👩","‍","👧","‍","👦"]' 43 | * console.log(segment(test)); // '["fi","t","🚀","👫","👨‍👩‍👧‍👦"]' 44 | * ``` 45 | * 46 | * Note, this utility segments the string into grapheme clusters using 47 | * `Intl.Segmenter`, a TC39 proposal which reached stage 4 in 2022, and may not 48 | * be supported in older environments. 49 | * 50 | * @param utf8 - the string for which to segment characters. 51 | */ 52 | export const segment = (utf8: string) => 53 | [...new Intl.Segmenter('en', { granularity: 'grapheme' }).segment(utf8)].map( 54 | (item) => item.segment, 55 | ); 56 | 57 | /** 58 | * Return the user-perceived character length of the given string, e.g.: 59 | * 60 | * ```js 61 | * const test = 'fit🚀👫👨‍👩‍👧‍👦' 62 | * console.log(test.length); // 17 63 | * console.log(length(test)); // 5 64 | * ``` 65 | * 66 | * Note, this utility segments the string into grapheme clusters using 67 | * `Intl.Segmenter`, a TC39 proposal which reached stage 4 in 2022, and may not 68 | * be supported in older environments. 69 | * 70 | * @param utf8 - the string for which to count the character length. 71 | */ 72 | export const length = (utf8: string) => segment(utf8).length; 73 | -------------------------------------------------------------------------------- /src/lib/key/key.ts: -------------------------------------------------------------------------------- 1 | export * from './bip39.js'; 2 | export * from './hd-key.js'; 3 | export * from './key-utils.js'; 4 | export * from './wallet-import-format.js'; 5 | -------------------------------------------------------------------------------- /src/lib/key/wallet-import-format.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { WalletImportFormatType } from '../lib.js'; 4 | import { 5 | Base58AddressError, 6 | BaseConversionError, 7 | decodePrivateKeyWif, 8 | encodePrivateKeyWif, 9 | hexToBin, 10 | sha256 as internalSha256, 11 | } from '../lib.js'; 12 | 13 | test('decodePrivateKeyWif: pass through errors', (t) => { 14 | t.deepEqual( 15 | decodePrivateKeyWif('not a key'), 16 | `${Base58AddressError.unknownCharacter} ${BaseConversionError.unknownCharacter} Unknown character: " ".`, 17 | ); 18 | }); 19 | 20 | const wifVectors = test.macro<[WalletImportFormatType, string, string]>({ 21 | // eslint-disable-next-line @typescript-eslint/max-params 22 | exec: (t, type, wif, key) => { 23 | t.deepEqual(encodePrivateKeyWif(hexToBin(key), type), wif); 24 | t.deepEqual(decodePrivateKeyWif(wif), { 25 | privateKey: hexToBin(key), 26 | type, 27 | }); 28 | t.deepEqual(encodePrivateKeyWif(hexToBin(key), type, internalSha256), wif); 29 | t.deepEqual(decodePrivateKeyWif(wif, internalSha256), { 30 | privateKey: hexToBin(key), 31 | type, 32 | }); 33 | }, 34 | title: (_, type, base58) => 35 | `encodePrivateKeyWif <-> decodePrivateKeyWif ${type} - ${base58.slice( 36 | 0, 37 | 6, 38 | )}...`, 39 | }); 40 | 41 | test( 42 | wifVectors, 43 | 'mainnet', 44 | 'L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi', 45 | '7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4', 46 | ); 47 | 48 | test( 49 | wifVectors, 50 | 'mainnet', 51 | 'KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2', 52 | '07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd', 53 | ); 54 | 55 | test( 56 | wifVectors, 57 | 'testnet', 58 | 'cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH', 59 | 'b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3', 60 | ); 61 | 62 | test( 63 | wifVectors, 64 | 'mainnetUncompressed', 65 | '5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr', 66 | 'eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19', 67 | ); 68 | 69 | test( 70 | wifVectors, 71 | 'testnetUncompressed', 72 | '9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko', 73 | '36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2', 74 | ); 75 | -------------------------------------------------------------------------------- /src/lib/language/language.ts: -------------------------------------------------------------------------------- 1 | export * from './compile.js'; 2 | export * from './language-utils.js'; 3 | export * from './language-types.js'; 4 | export * from './parse.js'; 5 | export * from './reduce.js'; 6 | export * from './resolve.js'; 7 | -------------------------------------------------------------------------------- /src/lib/language/parse.ts: -------------------------------------------------------------------------------- 1 | import { lossyNormalize } from '../format/format.js'; 2 | import type { ParseResult } from '../lib.js'; 3 | 4 | import { P } from './parsimmon.js'; 5 | 6 | /* eslint-disable sort-keys, @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ 7 | const cashAssemblyParser = P.createLanguage({ 8 | script: (r) => 9 | P.seqMap( 10 | P.optWhitespace, 11 | r.expression.sepBy(P.optWhitespace), 12 | P.optWhitespace, 13 | (_, expressions) => expressions, 14 | ).node('Script'), 15 | expression: (r) => 16 | P.alt( 17 | r.comment, 18 | r.push, 19 | r.evaluation, 20 | r.utf8, 21 | r.binary, 22 | r.hex, 23 | r.bigint, 24 | r.identifier, 25 | ), 26 | comment: (r) => 27 | P.alt(r.singleLineComment, r.multiLineComment).node('Comment'), 28 | singleLineComment: () => 29 | P.seqMap( 30 | P.string('//').desc("the start of a single-line comment ('//')"), 31 | P.regexp(/[^\n]*/u), 32 | (__, comment) => comment.trim(), 33 | ), 34 | multiLineComment: () => 35 | P.seqMap( 36 | P.string('/*').desc("the start of a multi-line comment ('/*')"), 37 | P.regexp(/[\s\S]*?\*\//u).desc( 38 | "the end of this multi-line comment ('*/')", 39 | ), 40 | (__, comment) => comment.slice(0, -'*/'.length).trim(), 41 | ), 42 | push: (r) => 43 | P.seqMap( 44 | P.string('<').desc("the start of a push statement ('<')"), 45 | r.script, 46 | P.string('>').desc("the end of this push statement ('>')"), 47 | (_, push) => push, 48 | ).node('Push'), 49 | evaluation: (r) => 50 | P.seqMap( 51 | P.string('$').desc("the start of an evaluation ('$')"), 52 | P.string('(').desc("the opening parenthesis of this evaluation ('(')"), 53 | r.script, 54 | P.string(')').desc("the closing parenthesis of this evaluation (')')"), 55 | (_, __, evaluation) => evaluation, 56 | ).node('Evaluation'), 57 | identifier: () => 58 | P.regexp(/[a-zA-Z_][.a-zA-Z0-9_-]*/u) 59 | .desc('a valid identifier') 60 | .node('Identifier'), 61 | utf8: () => 62 | P.alt( 63 | P.seqMap( 64 | P.string('"').desc('a double quote (")'), 65 | P.regexp(/[^"]*/u), 66 | P.string('"').desc('a closing double quote (")'), 67 | (__, literal) => literal, 68 | ), 69 | P.seqMap( 70 | P.string("'").desc("a single quote (')"), 71 | P.regexp(/[^']*/u), 72 | P.string("'").desc("a closing single quote (')"), 73 | (__, literal) => literal, 74 | ), 75 | ).node('UTF8Literal'), 76 | hex: () => 77 | P.seqMap( 78 | P.string('0x').desc("a hex literal ('0x...')"), 79 | P.regexp(/[0-9a-f]_*(?:_*[0-9a-f]_*[0-9a-f]_*)*[0-9a-f]/iu).desc( 80 | 'a valid hexadecimal string', 81 | ), 82 | (__, literal) => literal, 83 | ).node('HexLiteral'), 84 | binary: () => 85 | P.seqMap( 86 | P.string('0b').desc("a binary literal ('0b...')"), 87 | P.regexp(/[01]+(?:[01_]*[01]+)*/iu).desc('a string of binary digits'), 88 | (__, literal) => literal, 89 | ).node('BinaryLiteral'), 90 | bigint: () => 91 | P.regexp(/-?[0-9]+(?:[0-9_]*[0-9]+)*/u) 92 | .desc('an integer literal') 93 | .node('BigIntLiteral'), 94 | }); 95 | /* eslint-enable sort-keys, @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ 96 | 97 | export const parseScript = (script: string) => 98 | cashAssemblyParser.script.parse(lossyNormalize(script)) as ParseResult; 99 | -------------------------------------------------------------------------------- /src/lib/language/reduce.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { reduceScript, stringify } from '../lib.js'; 4 | 5 | test('reduceScript: does not throw on empty array', (t) => { 6 | const reduced = reduceScript([]); 7 | t.deepEqual( 8 | reduced, 9 | { 10 | bytecode: Uint8Array.of(), 11 | range: { 12 | endColumn: 0, 13 | endLineNumber: 0, 14 | startColumn: 0, 15 | startLineNumber: 0, 16 | }, 17 | script: [], 18 | }, 19 | stringify(reduced), 20 | ); 21 | }); 22 | 23 | test('reduceScript: resolution error', (t) => { 24 | const reduced = reduceScript([ 25 | { 26 | range: { 27 | endColumn: 8, 28 | endLineNumber: 1, 29 | startColumn: 1, 30 | startLineNumber: 1, 31 | }, 32 | type: 'error', 33 | value: 'Unknown identifier "unknown".', 34 | }, 35 | ]); 36 | t.deepEqual( 37 | reduced, 38 | { 39 | bytecode: Uint8Array.of(), 40 | errors: [ 41 | { 42 | error: 43 | 'Tried to reduce a CashAssembly script with resolution errors: Unknown identifier "unknown".', 44 | range: { 45 | endColumn: 8, 46 | endLineNumber: 1, 47 | startColumn: 1, 48 | startLineNumber: 1, 49 | }, 50 | }, 51 | ], 52 | range: { 53 | endColumn: 8, 54 | endLineNumber: 1, 55 | startColumn: 1, 56 | startLineNumber: 1, 57 | }, 58 | script: [ 59 | { 60 | bytecode: Uint8Array.of(), 61 | errors: [ 62 | { 63 | error: 64 | 'Tried to reduce a CashAssembly script with resolution errors: Unknown identifier "unknown".', 65 | range: { 66 | endColumn: 8, 67 | endLineNumber: 1, 68 | startColumn: 1, 69 | startLineNumber: 1, 70 | }, 71 | }, 72 | ], 73 | range: { 74 | endColumn: 8, 75 | endLineNumber: 1, 76 | startColumn: 1, 77 | startLineNumber: 1, 78 | }, 79 | }, 80 | ], 81 | }, 82 | stringify(reduced), 83 | ); 84 | }); 85 | 86 | test('reduceScript: invalid ResolvedScript', (t) => { 87 | t.throws(() => 88 | reduceScript([ 89 | { 90 | range: { 91 | endColumn: 2, 92 | endLineNumber: 1, 93 | startColumn: 1, 94 | startLineNumber: 1, 95 | }, 96 | type: "uncaught because the consumer isn't using TypeScript" as 'error', 97 | value: 'Another kind of value', 98 | }, 99 | ]), 100 | ); 101 | }); 102 | -------------------------------------------------------------------------------- /src/lib/language/resolve.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { 4 | CashAssemblyScriptSegment, 5 | IdentifierResolutionFunction, 6 | ResolvedScript, 7 | } from '../lib.js'; 8 | import { 9 | IdentifierResolutionType, 10 | parseScript, 11 | resolveScriptSegment, 12 | } from '../lib.js'; 13 | 14 | test('resolveScriptSegment: error on unrecognized parse results', (t) => { 15 | const segment = { 16 | end: { 17 | column: 24, 18 | line: 1, 19 | offset: 23, 20 | }, 21 | name: 'Script', 22 | start: { 23 | column: 1, 24 | line: 1, 25 | offset: 0, 26 | }, 27 | value: [ 28 | { 29 | end: { 30 | column: 24, 31 | line: 1, 32 | offset: 23, 33 | }, 34 | name: 'Unknown' as 'Identifier', 35 | start: { 36 | column: 1, 37 | line: 1, 38 | offset: 0, 39 | }, 40 | value: 'unrecognized_expression', 41 | }, 42 | ], 43 | } as CashAssemblyScriptSegment; 44 | t.deepEqual( 45 | resolveScriptSegment(segment, () => ({ 46 | bytecode: Uint8Array.of(), 47 | status: true, 48 | type: IdentifierResolutionType.variable, 49 | })), 50 | [ 51 | { 52 | range: { 53 | endColumn: 24, 54 | endLineNumber: 1, 55 | startColumn: 1, 56 | startLineNumber: 1, 57 | }, 58 | type: 'error', 59 | value: 'Unrecognized segment: Unknown', 60 | }, 61 | ], 62 | ); 63 | }); 64 | 65 | test('resolveScriptSegment: marks unknown identifier types', (t) => { 66 | const parseResult = parseScript('some_identifier'); 67 | if (!parseResult.status) { 68 | t.fail('Parse failed.'); 69 | return; 70 | } 71 | const malformedResolver: IdentifierResolutionFunction = () => ({ 72 | bytecode: Uint8Array.of(), 73 | status: true, 74 | type: 'unknown-type' as IdentifierResolutionType.variable, 75 | }); 76 | const resolved = resolveScriptSegment(parseResult.value, malformedResolver); 77 | t.deepEqual(resolved, [ 78 | { 79 | range: { 80 | endColumn: 16, 81 | endLineNumber: 1, 82 | startColumn: 1, 83 | startLineNumber: 1, 84 | }, 85 | type: 'bytecode' as const, 86 | unknown: 'some_identifier', 87 | value: Uint8Array.of(), 88 | }, 89 | ] as unknown as ResolvedScript); 90 | }); 91 | -------------------------------------------------------------------------------- /src/lib/lib.ts: -------------------------------------------------------------------------------- 1 | export * from './address/address.js'; 2 | export * from './bin/bin.js'; 3 | export * from './compiler/compiler.js'; 4 | export * from './crypto/crypto.js'; 5 | export * from './engine/engine.js'; 6 | export * from './format/format.js'; 7 | export * from './key/key.js'; 8 | export * from './language/language.js'; 9 | export * from './message/message.js'; 10 | export * from './schema/schema.js'; 11 | export * from './transaction/transaction.js'; 12 | export * from './vm/vm.js'; 13 | export * from './vmb-tests/vmb-tests.js'; 14 | -------------------------------------------------------------------------------- /src/lib/message/message.ts: -------------------------------------------------------------------------------- 1 | export * from './read-components.js'; 2 | export * from './transaction-encoding.js'; 3 | export * from './transaction-types.js'; 4 | -------------------------------------------------------------------------------- /src/lib/message/read-components.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { ReadResult } from '../lib.js'; 4 | import { 5 | bigIntToBinUint64LE, 6 | hexToBin, 7 | numberToBinUint32LE, 8 | readBytes, 9 | ReadBytesError, 10 | readUint32LE, 11 | ReadUint32LEError, 12 | readUint64LE, 13 | ReadUint64LEError, 14 | /* 15 | * readCompactUintPrefixedBin, 16 | * readRemainingBytes, 17 | * readUint64LE, 18 | */ 19 | } from '../lib.js'; 20 | 21 | test('readBytes', (t) => { 22 | const bin = hexToBin('00010203'); 23 | t.deepEqual( 24 | readBytes(1)({ bin, index: 4 }), 25 | `${ReadBytesError.insufficientLength} Bytes requested: 1; remaining bytes: 0`, 26 | ); 27 | const notMutated = { bin, index: 0 }; 28 | const result = readBytes(1)(notMutated); 29 | t.deepEqual(result, { 30 | position: { bin, index: 1 }, 31 | result: hexToBin('00'), 32 | }); 33 | t.deepEqual(notMutated, { bin, index: 0 }); 34 | const newBin = (result as ReadResult).result; 35 | newBin.fill(0xff); 36 | t.deepEqual(newBin, hexToBin('ff')); 37 | t.deepEqual(notMutated.bin, bin); 38 | t.deepEqual(readBytes(3)({ bin, index: 1 }), { 39 | position: { bin, index: 4 }, 40 | result: hexToBin('010203'), 41 | }); 42 | }); 43 | 44 | test('readUint32LE', (t) => { 45 | const bin = hexToBin('00010203'); 46 | const num = 0x03020100; 47 | const result = { 48 | position: { bin, index: 4 }, 49 | result: num, 50 | }; 51 | t.deepEqual(readUint32LE({ bin, index: 0 }), result); 52 | t.deepEqual(bin, numberToBinUint32LE(num)); 53 | result.position.bin.fill(0xff); 54 | const mutates = hexToBin('ffffffff'); 55 | t.deepEqual(bin, mutates); 56 | t.deepEqual( 57 | readUint32LE({ bin: hexToBin('0001'), index: 0 }), 58 | `${ReadUint32LEError.insufficientBytes} Remaining bytes: 2`, 59 | ); 60 | }); 61 | 62 | test('readUint64LE', (t) => { 63 | const bin = hexToBin('00010203ffffffff'); 64 | const num = 0xffff_ffff_0302_0100n; 65 | const result = { 66 | position: { bin, index: 8 }, 67 | result: num, 68 | }; 69 | t.deepEqual(readUint64LE({ bin, index: 0 }), result); 70 | t.deepEqual(bin, bigIntToBinUint64LE(num)); 71 | result.position.bin.fill(0xff); 72 | const mutates = hexToBin('ffffffffffffffff'); 73 | t.deepEqual(bin, mutates); 74 | t.deepEqual( 75 | readUint64LE({ bin: hexToBin('00010203'), index: 0 }), 76 | `${ReadUint64LEError.insufficientBytes} Remaining bytes: 4`, 77 | ); 78 | }); 79 | 80 | test.todo('readCompactUintPrefixedBin'); 81 | test.todo('readRemainingBytes'); 82 | -------------------------------------------------------------------------------- /src/lib/schema/README.md: -------------------------------------------------------------------------------- 1 | # Libauth Schemas 2 | 3 | Much of this directory is automatically generated by the `gen:schema:*` package scripts. **Any changes to generated content will be overwritten automatically**. To modify a schema, modify the underlying type from which it is generated, then re-generate it. For details, review the respective `gen:schema:*` package scripts. 4 | -------------------------------------------------------------------------------- /src/lib/schema/ajv/ajv-types.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export type AjvError< 3 | Keyword = string, 4 | Params = { [paramName: string]: number | string }, 5 | > = { 6 | keyword: Keyword; 7 | instancePath: string; 8 | schemaPath: string; 9 | params: Params; 10 | message: string; 11 | }; 12 | 13 | export type LibauthAjvError = 14 | | AjvError<'additionalProperties', { additionalProperty: string }> 15 | | AjvError<'required', { missingProperty: string }> 16 | | AjvError<'type', { type: string }>; 17 | 18 | /** 19 | * Note: these types cover only Libauth use cases; other `ajv` error types are 20 | * possible using other settings. 21 | */ 22 | // eslint-disable-next-line functional/no-mixed-types 23 | export type AjvValidator = { 24 | ( 25 | data: unknown, 26 | dataCxt?: { 27 | instancePath?: string; 28 | parentData: any; 29 | parentDataProperty: any; 30 | rootData?: any; 31 | }, 32 | ): data is T; 33 | errors?: LibauthAjvError[] | null; 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/schema/ajv/ajv-utils.ts: -------------------------------------------------------------------------------- 1 | import { lossyNormalize } from '../../format/format.js'; 2 | 3 | import type { AjvValidator, LibauthAjvError } from './ajv-types.js'; 4 | import walletTemplateValidator from './validate-wallet-template.js'; 5 | 6 | const avjErrorsToDescription = (errors: LibauthAjvError[]): string => 7 | // TODO: translate instancePath 8 | errors.map((error) => `${error.instancePath}: ${error.message}`).join(','); 9 | 10 | /** 11 | * Given an untrusted JSON string or object and an AJV validator, verify that 12 | * the untrusted value is of the expected shape. Note, this method first 13 | * normalizes all characters in the input using `Normalization Form KC` 14 | * (Compatibility Decomposition, followed by Canonical Composition). 15 | */ 16 | export const ajvStandaloneJsonParse = ( 17 | untrustedJsonOrObject: unknown, 18 | validator: AjvValidator, 19 | ) => { 20 | // eslint-disable-next-line functional/no-try-statements 21 | try { 22 | const stringified = 23 | typeof untrustedJsonOrObject === 'string' 24 | ? untrustedJsonOrObject 25 | : JSON.stringify(untrustedJsonOrObject); 26 | const normalized = lossyNormalize(stringified); 27 | const parsed = JSON.parse(normalized) as unknown; 28 | if (validator(parsed)) { 29 | return parsed; 30 | } 31 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 32 | return avjErrorsToDescription(walletTemplateValidator.errors!); 33 | } catch (e) { 34 | return `Invalid JSON. ${String(e)}`; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/lib/schema/ajv/validate-bcmr.d.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRegistry } from '../../lib.js'; 2 | 3 | import type { AjvValidator } from './ajv-types.js'; 4 | 5 | declare const validator: AjvValidator; 6 | // eslint-disable-next-line import/no-default-export 7 | export default validator; 8 | -------------------------------------------------------------------------------- /src/lib/schema/ajv/validate-wallet-template.d.ts: -------------------------------------------------------------------------------- 1 | import type { WalletTemplate } from '../../lib.js'; 2 | 3 | import type { AjvValidator } from './ajv-types.js'; 4 | 5 | declare const validator: AjvValidator; 6 | // eslint-disable-next-line import/no-default-export 7 | export default validator; 8 | -------------------------------------------------------------------------------- /src/lib/schema/bcmr.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { MetadataRegistry } from '../lib.js'; 4 | import { importMetadataRegistry } from '../lib.js'; 5 | 6 | test('importMetadataRegistry: accepts templates as either JSON strings or pre-parsed objects', (t) => { 7 | const registry: MetadataRegistry = { 8 | latestRevision: '', 9 | registryIdentity: { name: '' }, 10 | version: { major: 0, minor: 0, patch: 0 }, 11 | }; 12 | t.deepEqual(registry, importMetadataRegistry(registry)); 13 | t.deepEqual(registry, importMetadataRegistry(JSON.stringify(registry))); 14 | }); 15 | -------------------------------------------------------------------------------- /src/lib/schema/bcmr.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRegistry } from '../lib.js'; 2 | 3 | import { ajvStandaloneJsonParse } from './ajv/ajv-utils.js'; 4 | // eslint-disable-next-line import/no-internal-modules 5 | import walletTemplateValidator from './ajv/validate-bcmr.js'; 6 | 7 | /** 8 | * Safely parse and validate a Bitcoin Cash Metadata Registry (BCMR), returning 9 | * either an error message as a string or a valid {@link MetadataRegistry}. The 10 | * registry may be provided either as an untrusted JSON string or as a 11 | * pre-parsed object. 12 | * 13 | * This method validates both the structure and the contents of a registry: 14 | * - All properties and sub-properties are verified to be of the expected type. 15 | * - The registry contains no unknown properties. 16 | * 17 | * @param untrustedJsonOrObject - the JSON string or object to validate as 18 | * metadata registry 19 | */ 20 | export const importMetadataRegistry = ( 21 | untrustedJsonOrObject: unknown, 22 | ): MetadataRegistry | string => { 23 | const errorPrefix = `Metadata Registry import failed:`; 24 | const registry = ajvStandaloneJsonParse( 25 | untrustedJsonOrObject, 26 | walletTemplateValidator, 27 | ); 28 | if (typeof registry === 'string') { 29 | return `${errorPrefix}${registry}`; 30 | } 31 | 32 | // TODO: additional validation? 33 | return registry; 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/schema/schema.ts: -------------------------------------------------------------------------------- 1 | export * from './bcmr.js'; 2 | export * from './wallet-template.js'; 3 | -------------------------------------------------------------------------------- /src/lib/schema/wallet-template.ts: -------------------------------------------------------------------------------- 1 | import type { WalletTemplate } from '../lib.js'; 2 | 3 | import { ajvStandaloneJsonParse } from './ajv/ajv-utils.js'; 4 | // eslint-disable-next-line import/no-internal-modules 5 | import walletTemplateValidator from './ajv/validate-wallet-template.js'; 6 | 7 | /** 8 | * Safely parse and validate a wallet template, returning either an 9 | * error message as a string or a valid {@link WalletTemplate}. The 10 | * template may be provided either as an untrusted JSON string or as a 11 | * pre-parsed object. 12 | * 13 | * This method validates both the structure and the contents of a template: 14 | * - All properties and sub-properties are verified to be of the expected type. 15 | * - The template contains no unknown properties. 16 | * - The ID of each entity, script, and scenario is confirmed to be unique. 17 | * - Script IDs referenced by entities and other scripts (via `unlocks`) are 18 | * confirmed to exist. 19 | * - The derivation paths of each HdKey are validated against each other. 20 | * 21 | * This method does not validate the CashAssembly contents of scripts (by 22 | * attempting compilation, evaluating {@link WalletTemplateScriptTest}s, 23 | * or testing scenario generation). 24 | * 25 | * @param untrustedJsonOrObject - the JSON string or object to validate as a 26 | * wallet template 27 | */ 28 | export const importWalletTemplate = ( 29 | untrustedJsonOrObject: unknown, 30 | ): WalletTemplate | string => { 31 | const errorPrefix = `Wallet template import failed:`; 32 | const template = ajvStandaloneJsonParse( 33 | untrustedJsonOrObject, 34 | walletTemplateValidator, 35 | ); 36 | if (typeof template === 'string') { 37 | return `${errorPrefix}${template}`; 38 | } 39 | 40 | // TODO: add back other validation 41 | return template; 42 | }; 43 | -------------------------------------------------------------------------------- /src/lib/transaction/estimate-transaction.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AnyCompilerConfiguration, 3 | Compiler, 4 | TransactionGenerationAttempt, 5 | TransactionTemplateEstimated, 6 | } from '../lib.js'; 7 | 8 | /** 9 | * Generate an "estimated" transaction – an invalid transaction that matches 10 | * the expected size of the final transaction. 11 | * 12 | * This is useful for estimating the required transaction fee before attempting 13 | * to properly sign the transaction – especially for transactions that require 14 | * approval and signatures from multiple entities. An estimated transaction can 15 | * be quickly generated by a single entity without requiring any variable data 16 | * (like private keys or wallet data). 17 | * 18 | * @remarks 19 | * Like `generateTransaction` This method accepts a transaction template and 20 | * generates bytecode given all compilation directives, however, each directive 21 | * must also contain an `estimate` scenario ID that will be used as the `data` 22 | * in the compilation. The template must also include a 23 | * `totalInputValueSatoshis` value (the total satoshi value of all transaction 24 | * inputs). 25 | * 26 | * The `valueSatoshis` property of each output is also optional. (All output 27 | * `valueSatoshis` properties will be set to `excessiveSatoshis` to guarantee the 28 | * transaction's invalidity). 29 | *s 30 | * @param template - the transaction template from which to generate the 31 | * "estimated" transaction 32 | */ 33 | export const estimateTransaction = < 34 | CompilerType extends Compiler< 35 | unknown, 36 | AnyCompilerConfiguration, 37 | unknown 38 | >, 39 | ProgramState, 40 | >( 41 | template: TransactionTemplateEstimated, 42 | ): TransactionGenerationAttempt => 43 | // TODO: estimateTransaction 44 | typeof template === 'object' 45 | ? { completions: [], errors: [], stage: 'inputs', success: false } 46 | : { completions: [], errors: [], stage: 'inputs', success: false }; 47 | -------------------------------------------------------------------------------- /src/lib/transaction/fixtures/generate-templates.spec.helper.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, functional/no-expression-statements */ 2 | import { stringify, walletTemplateP2pkh } from '../../lib.js'; 3 | 4 | import { twoOfTwoRecoverable } from './template.2-of-2-recoverable.spec.helper.js'; 5 | import { twoOfThree } from './template.2-of-3.spec.helper.js'; 6 | import { cashChannels } from './template.cash-channels-v1.spec.helper.js'; 7 | import { sigOfSig } from './template.sig-of-sig.spec.helper.js'; 8 | 9 | // eslint-disable-next-line complexity, functional/no-return-void 10 | const printTemplate = (template: string) => { 11 | switch (template) { 12 | case 'p2pkh': 13 | console.log(stringify(walletTemplateP2pkh)); 14 | return; 15 | case '2-of-3': 16 | console.log(stringify(twoOfThree)); 17 | return; 18 | case '2-of-2-recoverable': 19 | console.log(stringify(twoOfTwoRecoverable)); 20 | return; 21 | case 'sig-of-sig': 22 | console.log(stringify(sigOfSig)); 23 | return; 24 | case 'cash-channels-v1': 25 | console.log(stringify(cashChannels)); 26 | return; 27 | 28 | default: 29 | console.error('unknown template'); 30 | process.exit(1); 31 | } 32 | }; 33 | 34 | const [, , arg] = process.argv; 35 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 36 | printTemplate(arg!); 37 | -------------------------------------------------------------------------------- /src/lib/transaction/fixtures/template.2-of-3.spec.helper.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase, @typescript-eslint/naming-convention */ 2 | import type { WalletTemplate, WalletTemplateEntity } from '../../lib.js'; 3 | 4 | const createSigner = ( 5 | name: string, 6 | signerIndex: string, 7 | scripts: string[], 8 | ): WalletTemplateEntity => ({ 9 | name, 10 | scripts: ['lock', ...scripts], 11 | variables: { [`key${signerIndex}`]: { type: 'HdKey' } }, 12 | }); 13 | 14 | /** 15 | * 2-of-3 P2SH 16 | * This is a mostly-hard-coded 2-of-3 example. A more general function could be written to generate m-of-n wallets 17 | */ 18 | export const twoOfThree: WalletTemplate = { 19 | ...{ name: '2-of-3 Multisig' }, 20 | $schema: 'https://libauth.org/schemas/wallet-template-v0.schema.json', 21 | entities: { 22 | signer_1: createSigner('Signer 1', '1', ['1_and_2', '1_and_3']), 23 | signer_2: createSigner('Signer 2', '2', ['1_and_2', '2_and_3']), 24 | signer_3: createSigner('Signer 3', '3', ['1_and_3', '2_and_3']), 25 | }, 26 | scripts: { 27 | '1_and_2': { 28 | name: 'Cosigner 1 & 2', 29 | script: 30 | 'OP_0\n\n', 31 | unlocks: 'lock', 32 | }, 33 | '1_and_3': { 34 | name: 'Cosigner 1 & 3', 35 | script: 36 | 'OP_0\n\n', 37 | unlocks: 'lock', 38 | }, 39 | '2_and_3': { 40 | name: 'Cosigner 2 & 3', 41 | script: 42 | 'OP_0\n\n', 43 | unlocks: 'lock', 44 | }, 45 | lock: { 46 | lockingType: 'p2sh20', 47 | name: '2-of-3 Vault', 48 | script: 49 | 'OP_2\n\n\n\nOP_3\nOP_CHECKMULTISIG', 50 | }, 51 | }, 52 | supported: ['BCH_2021_05', 'BCH_2022_05'], 53 | version: 0, 54 | }; 55 | -------------------------------------------------------------------------------- /src/lib/transaction/fixtures/template.sig-of-sig.spec.helper.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase, @typescript-eslint/naming-convention */ 2 | import type { WalletTemplate } from '../../lib.js'; 3 | 4 | export const sigOfSig: WalletTemplate = { 5 | ...{ 6 | name: 'Sig-of-Sig Vault (2-of-2)', 7 | }, 8 | description: 9 | 'An unusual example of a template that must be signed in a specific order: Second Signer may only sign after First Signer. This construction could be useful in scenarios where a particular signing order must be guaranteed, e.g. proving that a "most-responsible" party only signed for a transaction knowing they were the final signer.', 10 | entities: { 11 | signer_1: { 12 | name: 'First Signer', 13 | variables: { 14 | first: { 15 | type: 'HdKey', 16 | }, 17 | }, 18 | }, 19 | signer_2: { 20 | name: 'Second Signer', 21 | variables: { 22 | second: { 23 | type: 'HdKey', 24 | }, 25 | }, 26 | }, 27 | }, 28 | scripts: { 29 | first_signature: { 30 | script: 'first.ecdsa_signature.all_outputs', 31 | }, 32 | lock: { 33 | lockingType: 'p2sh20', 34 | name: 'Sig-of-Sig Vault', 35 | script: 36 | 'OP_2 OP_PICK OP_CHECKDATASIGVERIFY OP_DUP OP_HASH160 <$( OP_HASH160)> OP_EQUALVERIFY OP_CHECKSIG', 37 | }, 38 | spend: { 39 | script: 40 | ' ', 41 | unlocks: 'lock', 42 | }, 43 | }, 44 | supported: ['BCH_2021_05', 'BCH_2022_05'], 45 | version: 0, 46 | }; 47 | -------------------------------------------------------------------------------- /src/lib/transaction/fixtures/templates/2-of-3.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2-of-3 Multisig", 3 | "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", 4 | "entities": { 5 | "signer_1": { 6 | "name": "Signer 1", 7 | "scripts": ["lock", "1_and_2", "1_and_3"], 8 | "variables": { 9 | "key1": { 10 | "type": "HdKey" 11 | } 12 | } 13 | }, 14 | "signer_2": { 15 | "name": "Signer 2", 16 | "scripts": ["lock", "1_and_2", "2_and_3"], 17 | "variables": { 18 | "key2": { 19 | "type": "HdKey" 20 | } 21 | } 22 | }, 23 | "signer_3": { 24 | "name": "Signer 3", 25 | "scripts": ["lock", "1_and_3", "2_and_3"], 26 | "variables": { 27 | "key3": { 28 | "type": "HdKey" 29 | } 30 | } 31 | } 32 | }, 33 | "scripts": { 34 | "1_and_2": { 35 | "name": "Cosigner 1 & 2", 36 | "script": "OP_0\n\n", 37 | "unlocks": "lock" 38 | }, 39 | "1_and_3": { 40 | "name": "Cosigner 1 & 3", 41 | "script": "OP_0\n\n", 42 | "unlocks": "lock" 43 | }, 44 | "2_and_3": { 45 | "name": "Cosigner 2 & 3", 46 | "script": "OP_0\n\n", 47 | "unlocks": "lock" 48 | }, 49 | "lock": { 50 | "lockingType": "p2sh20", 51 | "name": "2-of-3 Vault", 52 | "script": "OP_2\n\n\n\nOP_3\nOP_CHECKMULTISIG" 53 | } 54 | }, 55 | "supported": ["BCH_2021_05", "BCH_2022_05"], 56 | "version": 0 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/transaction/fixtures/templates/p2pkh.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", 3 | "description": "A standard single-factor wallet template that uses Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use on the network.\n\nThis P2PKH template uses BCH Schnorr signatures, reducing the size of transactions. Because the template uses a Hierarchical Deterministic (HD) key, it also supports watch-only clients.", 4 | "entities": { 5 | "owner": { 6 | "description": "The individual who can spend from this wallet.", 7 | "name": "Owner", 8 | "scripts": ["lock", "unlock"], 9 | "variables": { 10 | "key": { 11 | "description": "The private key that controls this wallet.", 12 | "name": "Key", 13 | "type": "HdKey" 14 | } 15 | } 16 | } 17 | }, 18 | "name": "Single Signature (P2PKH)", 19 | "scripts": { 20 | "lock": { 21 | "lockingType": "standard", 22 | "name": "P2PKH Lock", 23 | "script": "OP_DUP\nOP_HASH160 <$( OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG" 24 | }, 25 | "unlock": { 26 | "name": "Unlock", 27 | "script": "\n", 28 | "unlocks": "lock" 29 | } 30 | }, 31 | "supported": ["BCH_2020_05", "BCH_2021_05", "BCH_2022_05"], 32 | "version": 0 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/transaction/fixtures/templates/sig-of-sig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sig-of-Sig Vault (2-of-2)", 3 | "description": "An unusual example of a template that must be signed in a specific order: Second Signer may only sign after First Signer. This construction could be useful in scenarios where a particular signing order must be guaranteed, e.g. proving that a \"most-responsible\" party only signed for a transaction knowing they were the final signer.", 4 | "entities": { 5 | "signer_1": { 6 | "name": "First Signer", 7 | "variables": { 8 | "first": { 9 | "type": "HdKey" 10 | } 11 | } 12 | }, 13 | "signer_2": { 14 | "name": "Second Signer", 15 | "variables": { 16 | "second": { 17 | "type": "HdKey" 18 | } 19 | } 20 | } 21 | }, 22 | "scripts": { 23 | "first_signature": { 24 | "script": "first.ecdsa_signature.all_outputs" 25 | }, 26 | "lock": { 27 | "lockingType": "p2sh20", 28 | "name": "Sig-of-Sig Vault", 29 | "script": "OP_2 OP_PICK OP_CHECKDATASIGVERIFY OP_DUP OP_HASH160 <$( OP_HASH160)> OP_EQUALVERIFY OP_CHECKSIG" 30 | }, 31 | "spend": { 32 | "script": " ", 33 | "unlocks": "lock" 34 | } 35 | }, 36 | "supported": ["BCH_2021_05", "BCH_2022_05"], 37 | "version": 0 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/transaction/generate-transaction.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { privateKeyM0 } from '../compiler/compiler-bch/compiler-bch.e2e.spec.helper.js'; 4 | import { 5 | CashAddressNetworkPrefix, 6 | decodeTransaction, 7 | generateTransaction, 8 | hexToBin, 9 | importWalletTemplate, 10 | lockingBytecodeToCashAddress, 11 | walletTemplateToCompilerBCH, 12 | } from '../lib.js'; 13 | 14 | const maybeP2pkhTemplate: unknown = { 15 | entities: { 16 | ownerEntity: { 17 | name: 'Owner', 18 | scripts: ['lock', 'unlock'], 19 | variables: { 20 | owner: { 21 | description: 'The private key that controls this wallet.', 22 | name: "Owner's Key", 23 | type: 'Key', 24 | }, 25 | }, 26 | }, 27 | }, 28 | scripts: { 29 | celebrate: { 30 | script: 'OP_RETURN <"hello world">', 31 | }, 32 | lock: { 33 | lockingType: 'standard', 34 | name: 'P2PKH Lock', 35 | script: 36 | 'OP_DUP\nOP_HASH160 <$( OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG', 37 | }, 38 | unlock: { 39 | name: 'Unlock', 40 | script: '\n', 41 | unlocks: 'lock', 42 | }, 43 | }, 44 | supported: ['BCH_2022_05'], 45 | version: 0, 46 | }; 47 | 48 | test('createCompilerBCH: generateTransaction', (t) => { 49 | const p2pkhTemplate = importWalletTemplate(maybeP2pkhTemplate); 50 | 51 | if (typeof p2pkhTemplate === 'string') { 52 | t.fail(p2pkhTemplate); 53 | return; 54 | } 55 | 56 | const p2pkh = walletTemplateToCompilerBCH(p2pkhTemplate); 57 | const lockingBytecode = p2pkh.generateBytecode({ 58 | data: { keys: { privateKeys: { owner: privateKeyM0 } } }, 59 | scriptId: 'lock', 60 | }); 61 | 62 | if (!lockingBytecode.success) { 63 | t.log(lockingBytecode.errors); 64 | t.fail(); 65 | return; 66 | } 67 | 68 | t.deepEqual( 69 | lockingBytecodeToCashAddress({ 70 | bytecode: lockingBytecode.bytecode, 71 | prefix: CashAddressNetworkPrefix.testnet, 72 | }), 73 | { address: 'bchtest:qq2azmyyv6dtgczexyalqar70q036yund53jvfde0x' }, 74 | ); 75 | 76 | const utxoOutpointTransactionHash = hexToBin( 77 | '68127de83d2ab77d7f5fd8d2ac6181d94473c0cbb2d0776084bf28884f6ecd77', 78 | ); 79 | 80 | const valueSatoshis = 1000000; 81 | const result = generateTransaction({ 82 | inputs: [ 83 | { 84 | outpointIndex: 1, 85 | outpointTransactionHash: utxoOutpointTransactionHash, 86 | sequenceNumber: 0, 87 | unlockingBytecode: { 88 | compiler: p2pkh, 89 | data: { 90 | keys: { privateKeys: { owner: privateKeyM0 } }, 91 | }, 92 | script: 'unlock', 93 | valueSatoshis: BigInt(valueSatoshis), 94 | }, 95 | }, 96 | ], 97 | locktime: 0, 98 | outputs: [ 99 | { 100 | lockingBytecode: { 101 | compiler: p2pkh, 102 | script: 'celebrate', 103 | }, 104 | valueSatoshis: 0n, 105 | }, 106 | ], 107 | version: 2, 108 | }); 109 | 110 | if (!result.success) { 111 | t.log(result.errors); 112 | t.fail(); 113 | return; 114 | } 115 | 116 | t.deepEqual(result, { 117 | success: true, 118 | transaction: decodeTransaction( 119 | hexToBin( 120 | '020000000177cd6e4f8828bf846077d0b2cbc07344d98161acd2d85f7f7db72a3de87d1268010000006441f87a1dc0fb4a30443fdfcc678e713d99cffb963bd52b497377e81abe2cc2b5ac6e9837fab0a23f4d05fd06b80e7673a68bfa8d2f66b7ec5537e88696d7bae1b841210376ea9e36a75d2ecf9c93a0be76885e36f822529db22acfdc761c9b5b4544f5c5000000000100000000000000000d6a0b68656c6c6f20776f726c6400000000', 121 | ), 122 | ), 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /src/lib/transaction/transaction-e2e.p2pkh.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import type { CompilationData, TransactionCommon } from '../lib.js'; 4 | import { 5 | CashAddressNetworkPrefix, 6 | decodeTransactionCommon, 7 | generateTransaction, 8 | hexToBin, 9 | importWalletTemplate, 10 | lockingBytecodeToCashAddress, 11 | stringify, 12 | walletTemplateToCompilerBCH, 13 | } from '../lib.js'; 14 | 15 | import { 16 | hdPrivateKey, 17 | hdPublicKey, 18 | p2pkhJson, 19 | } from './transaction-e2e.spec.helper.js'; 20 | 21 | test('transaction e2e tests: P2PKH (walletTemplateP2pkhHd)', (t) => { 22 | const template = importWalletTemplate(p2pkhJson); 23 | if (typeof template === 'string') { 24 | t.fail(template); 25 | return; 26 | } 27 | 28 | const lockingScript = 'lock'; 29 | 30 | /** 31 | * Available to observer 32 | */ 33 | const lockingData: CompilationData = { 34 | hdKeys: { addressIndex: 0, hdPublicKeys: { owner: hdPublicKey } }, 35 | }; 36 | 37 | /** 38 | * Only available to owner 39 | */ 40 | const unlockingData: CompilationData = { 41 | hdKeys: { addressIndex: 0, hdPrivateKeys: { owner: hdPrivateKey } }, 42 | }; 43 | 44 | const compiler = walletTemplateToCompilerBCH(template); 45 | const lockingBytecode = compiler.generateBytecode({ 46 | data: lockingData, 47 | scriptId: lockingScript, 48 | }); 49 | 50 | if (!lockingBytecode.success) { 51 | t.fail(`lockingBytecode: ${stringify(lockingBytecode)}`); 52 | return; 53 | } 54 | 55 | t.deepEqual( 56 | lockingBytecodeToCashAddress({ 57 | bytecode: lockingBytecode.bytecode, 58 | prefix: CashAddressNetworkPrefix.testnet, 59 | }), 60 | { address: 'bchtest:qq2azmyyv6dtgczexyalqar70q036yund53jvfde0x' }, 61 | ); 62 | 63 | const utxoOutpointTransactionHash = hexToBin( 64 | '68127de83d2ab77d7f5fd8d2ac6181d94473c0cbb2d0776084bf28884f6ecd77', 65 | ); 66 | 67 | const satoshis = 1000000; 68 | const result = generateTransaction({ 69 | inputs: [ 70 | { 71 | outpointIndex: 1, 72 | outpointTransactionHash: utxoOutpointTransactionHash, 73 | sequenceNumber: 0, 74 | unlockingBytecode: { 75 | compiler, 76 | data: unlockingData, 77 | script: 'unlock', 78 | valueSatoshis: BigInt(satoshis), 79 | }, 80 | }, 81 | ], 82 | locktime: 0, 83 | outputs: [ 84 | { 85 | lockingBytecode: hexToBin('6a0b68656c6c6f20776f726c64'), 86 | valueSatoshis: 0n, 87 | }, 88 | ], 89 | version: 2, 90 | }); 91 | 92 | if (!result.success) { 93 | t.fail(`result: ${stringify(result)}`); 94 | return; 95 | } 96 | 97 | t.deepEqual(result, { 98 | success: true, 99 | transaction: decodeTransactionCommon( 100 | hexToBin( 101 | '020000000177cd6e4f8828bf846077d0b2cbc07344d98161acd2d85f7f7db72a3de87d1268010000006441f87a1dc0fb4a30443fdfcc678e713d99cffb963bd52b497377e81abe2cc2b5ac6e9837fab0a23f4d05fd06b80e7673a68bfa8d2f66b7ec5537e88696d7bae1b841210376ea9e36a75d2ecf9c93a0be76885e36f822529db22acfdc761c9b5b4544f5c5000000000100000000000000000d6a0b68656c6c6f20776f726c6400000000', 102 | ), 103 | ) as TransactionCommon, 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /src/lib/transaction/transaction-e2e.spec.helper.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-internal-modules */ 2 | import twoOfTwoRecoverableJson from './fixtures/templates/2-of-2-recoverable.json' assert { type: 'json' }; 3 | import twoOfThreeJson from './fixtures/templates/2-of-3.json' assert { type: 'json' }; 4 | import cashChannelsJson from './fixtures/templates/cash-channels-v1.json' assert { type: 'json' }; 5 | import p2pkhJson from './fixtures/templates/p2pkh.json' assert { type: 'json' }; 6 | import sigOfSigJson from './fixtures/templates/sig-of-sig.json' assert { type: 'json' }; 7 | 8 | export { 9 | twoOfTwoRecoverableJson, 10 | twoOfThreeJson, 11 | cashChannelsJson, 12 | p2pkhJson, 13 | sigOfSigJson, 14 | }; 15 | 16 | /** 17 | * `m` from `zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong` 18 | */ 19 | export const hdPrivateKey = 20 | 'xprv9s21ZrQH143K2PfMvkNViFc1fgumGqBew45JD8SxA59Jc5M66n3diqb92JjvaR61zT9P89Grys12kdtV4EFVo6tMwER7U2hcUmZ9VfMYPLC'; 21 | 22 | /** 23 | * `M` 24 | */ 25 | export const hdPublicKey = 26 | 'xpub661MyMwAqRbcEsjq2muW5PYkDikFgHuWJGzu1WrZiQgHUsgEeKMtGducsZe1iRsGAGNGDzmWYDM69ya24LMyR7mDhtzqQsc286XEQfM2kkV'; 27 | 28 | /** 29 | * `m/0'` 30 | */ 31 | export const hdPrivateKey0H = 32 | 'xprv9uNAm3qC8EoibXd3mwgQ9rxF8XJdfA9V9sF25DpLtYcf1u51Rpf8tfV4n2PdChXM97miXGiJf6UL2SsathXbiVbF6tSgmAVGM3XNb6Yn2EZ'; 33 | 34 | /** 35 | * HD public key for `m/0'` 36 | */ 37 | export const hdPublicKey0H = 38 | 'xpub68MXAZN5xcN1p1hWsyDQWztygZ984csLX6AcscDxSt9dthQ9yMyPSToYdJ24jCS5jaVMGSiLeGuP2cWvgKKYQsNXyg988XGGQYgk1FjDv4P'; 39 | 40 | /** 41 | * `m/1'` 42 | */ 43 | export const hdPrivateKey1H = 44 | 'xprv9uNAm3qC8EoicAhBeTEJyZE9DCPtmoQ2noPeFCiDxSqgTGV8ZwM89SAyw8Hhygg3Lkgqy53XM2nHtyMmhyVuDwxJupg9F3bemDN6XcdYNJd'; 45 | 46 | /** 47 | * HD public key for `m/1'` 48 | */ 49 | export const hdPublicKey1H = 50 | 'xpub68MXAZN5xcN1pemekUmKLhAsmEEPBG7tA2KF3b7qWnNfL4pH7UfNhEVTnQGeMZycFEGbS7w2gsyG7w77YWkoupkn6QZvRq7zeFS98Fic25b'; 51 | 52 | /** 53 | * `m/2'` 54 | */ 55 | export const hdPrivateKey2H = 56 | 'xprv9uNAm3qC8EoieXyZAVbnB5LeeCMKUKe8HcE9mi38ghdBrjgay1ixgt9Ve2EjvB4udeZdp7XeyvU4PyezsjVP6xmjBDcsidobgAYCBLVYyrj'; 57 | 58 | /** 59 | * HD public key for `m/2'` 60 | */ 61 | export const hdPublicKey2H = 62 | 'xpub68MXAZN5xcN1s242GX8nYDHPCEBosnMyeq9ka6SkF3AAjY1jWZ3DEgTyVKFwPNXcKsY1snLZV7rNcVdYUpL3eFWTrr8xnHhRW4XGmcgHdr7'; 63 | -------------------------------------------------------------------------------- /src/lib/transaction/transaction-e2e.templates.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { walletTemplateP2pkh } from '../lib.js'; 4 | 5 | import { twoOfTwoRecoverable } from './fixtures/template.2-of-2-recoverable.spec.helper.js'; 6 | import { twoOfThree } from './fixtures/template.2-of-3.spec.helper.js'; 7 | import { cashChannels } from './fixtures/template.cash-channels-v1.spec.helper.js'; 8 | import { sigOfSig } from './fixtures/template.sig-of-sig.spec.helper.js'; 9 | import { 10 | cashChannelsJson, 11 | p2pkhJson, 12 | sigOfSigJson, 13 | twoOfThreeJson, 14 | twoOfTwoRecoverableJson, 15 | } from './transaction-e2e.spec.helper.js'; 16 | 17 | const ignoreDefault = (anything: object) => ({ 18 | ...anything, 19 | default: true, 20 | }); 21 | 22 | test('example wallet templates are updated', (t) => { 23 | const solution = 24 | 'Run "yarn gen:templates" to correct this issue. (Note: watch tasks don\'t always update cached JSON imports when the source file changes. You may need to restart tsc.)'; 25 | t.deepEqual( 26 | ignoreDefault(twoOfTwoRecoverable), 27 | ignoreDefault(twoOfTwoRecoverableJson), 28 | `Inconsistency in twoOfTwoRecoverableJson. ${solution}`, 29 | ); 30 | t.deepEqual( 31 | ignoreDefault(twoOfThree), 32 | ignoreDefault(twoOfThreeJson), 33 | `Inconsistency in twoOfThreeJson. ${solution}`, 34 | ); 35 | t.deepEqual( 36 | ignoreDefault(cashChannels), 37 | ignoreDefault(cashChannelsJson), 38 | `Inconsistency in cashChannelsJson. ${solution}`, 39 | ); 40 | t.deepEqual( 41 | ignoreDefault(walletTemplateP2pkh), 42 | ignoreDefault(p2pkhJson), 43 | `Inconsistency in p2pkhJson. ${solution}`, 44 | ); 45 | t.deepEqual( 46 | ignoreDefault(sigOfSig), 47 | ignoreDefault(sigOfSigJson), 48 | `Inconsistency in sigOfSigJson. ${solution}`, 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /src/lib/transaction/transaction.ts: -------------------------------------------------------------------------------- 1 | export * from './generate-transaction.js'; 2 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2022/bch-2022-errors.ts: -------------------------------------------------------------------------------- 1 | export enum AuthenticationErrorBCH2022 { 2 | exceededMaximumVmNumberLength = 'Program attempted an OP_BIN2NUM operation on a byte sequence that cannot be encoded within the maximum VM Number length (8 bytes).', 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2022/bch-2022-types.ts: -------------------------------------------------------------------------------- 1 | import { hexToBin } from '../../../../format/format.js'; 2 | import type { 3 | AnyCompilerConfiguration, 4 | AuthenticationProgramCommon, 5 | AuthenticationProgramStateCommon, 6 | AuthenticationVirtualMachine, 7 | CompilationContext, 8 | Compiler, 9 | Input, 10 | Output, 11 | ResolvedTransactionCommon, 12 | TransactionCommon, 13 | } from '../../../../lib.js'; 14 | import { 15 | encodeTransactionCommon, 16 | hashTransactionP2pOrder, 17 | } from '../../../../message/message.js'; 18 | 19 | export type ResolvedTransactionBCH = ResolvedTransactionCommon; 20 | export type ResolvedTransaction = ResolvedTransactionBCH; 21 | export type AuthenticationProgramBCH = AuthenticationProgramCommon; 22 | export type AuthenticationProgram = AuthenticationProgramBCH; 23 | export type AuthenticationProgramStateBCH = AuthenticationProgramStateCommon; 24 | export type AuthenticationProgramState = AuthenticationProgramStateBCH; 25 | export type AuthenticationVirtualMachineBCH = AuthenticationVirtualMachine< 26 | ResolvedTransactionBCH, 27 | AuthenticationProgramBCH, 28 | AuthenticationProgramStateBCH 29 | >; 30 | export type TransactionBCH< 31 | InputType = Input, 32 | OutputType = Output, 33 | > = TransactionCommon; 34 | export type Transaction< 35 | InputType = Input, 36 | OutputType = Output, 37 | > = TransactionBCH; 38 | export type CompilationContextBCH = CompilationContext< 39 | TransactionBCH> 40 | >; 41 | export type CompilerBCH = Compiler< 42 | CompilationContextBCH, 43 | AnyCompilerConfiguration, 44 | AuthenticationProgramStateBCH 45 | >; 46 | 47 | // TODO: replace with scenarios 48 | export const createTestAuthenticationProgramBCH = ({ 49 | lockingBytecode, 50 | valueSatoshis, 51 | unlockingBytecode, 52 | }: Output & Pick) => { 53 | const testFundingTransaction: TransactionBCH = { 54 | inputs: [ 55 | { 56 | outpointIndex: 0xffffffff, 57 | outpointTransactionHash: hexToBin( 58 | '0000000000000000000000000000000000000000000000000000000000000000', 59 | ), 60 | sequenceNumber: 0xffffffff, 61 | unlockingBytecode: Uint8Array.of(0, 0), 62 | }, 63 | ], 64 | locktime: 0, 65 | outputs: [{ lockingBytecode, valueSatoshis }], 66 | version: 1, 67 | }; 68 | const testSpendingTransaction: TransactionBCH = { 69 | inputs: [ 70 | { 71 | outpointIndex: 0, 72 | outpointTransactionHash: hashTransactionP2pOrder( 73 | encodeTransactionCommon(testFundingTransaction), 74 | ), 75 | 76 | sequenceNumber: 0xffffffff, 77 | unlockingBytecode, 78 | }, 79 | ], 80 | locktime: 0, 81 | outputs: [{ lockingBytecode: Uint8Array.of(), valueSatoshis }], 82 | version: 1, 83 | }; 84 | return { 85 | inputIndex: 0, 86 | sourceOutputs: testFundingTransaction.outputs, 87 | transaction: testSpendingTransaction, 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2022/bch-2022-vm.ts: -------------------------------------------------------------------------------- 1 | import { createVirtualMachine } from '../../../virtual-machine.js'; 2 | 3 | import { createInstructionSetBCH2022 } from './bch-2022-instruction-set.js'; 4 | 5 | /** 6 | * Initialize a virtual machine using the BCH instruction set. 7 | * 8 | * @param standard - If `true`, the additional `isStandard` validations will be 9 | * enabled. Transactions that fail these rules are often called "non-standard" 10 | * and can technically be included by miners in valid blocks, but most network 11 | * nodes will refuse to relay them. (Default: `true`) 12 | */ 13 | export const createVirtualMachineBCH2022 = (standard = true) => 14 | createVirtualMachine(createInstructionSetBCH2022(standard)); 15 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2022/bch-2022.ts: -------------------------------------------------------------------------------- 1 | export * from './bch-2022-descriptions.js'; 2 | export * from './bch-2022-errors.js'; 3 | export * from './bch-2022-instruction-set.js'; 4 | export * from './bch-2022-opcodes.js'; 5 | export * from './bch-2022-types.js'; 6 | export * from './bch-2022-vm.js'; 7 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2023/bch-2023-consensus.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SigningSerializationTypeBCH, 3 | SigningSerializationTypesBCH, 4 | } from '../../common/common.js'; 5 | 6 | /** 7 | * Consensus settings for the `BCH_2023_05` instruction set. 8 | */ 9 | export enum ConsensusBCH2023 { 10 | /** 11 | * A.K.A. `MAX_SCRIPT_SIZE` 12 | */ 13 | maximumBytecodeLength = 10000, 14 | /** 15 | * A.K.A. `MAX_OP_RETURN_RELAY`, `nMaxDatacarrierBytes` 16 | */ 17 | maximumDataCarrierBytes = 223, 18 | /** 19 | * A.K.A. `MAX_OPS_PER_SCRIPT` 20 | */ 21 | maximumOperationCount = 201, 22 | /** 23 | * A.K.A. `MAX_STACK_SIZE` 24 | */ 25 | maximumStackDepth = 1000, 26 | /** 27 | * A.K.A. `MAX_SCRIPT_ELEMENT_SIZE` 28 | */ 29 | maximumStackItemLength = 520, 30 | /** 31 | * A.K.A. `MAX_STANDARD_VERSION` 32 | */ 33 | maximumStandardVersion = 2, 34 | /** 35 | * A.K.A. `MAX_TX_IN_SCRIPT_SIG_SIZE` 36 | */ 37 | maximumStandardUnlockingBytecodeLength = 1650, 38 | /** 39 | * Transactions smaller than 65 bytes are forbidden to prevent exploits of the 40 | * transaction Merkle tree design. 41 | * 42 | * A.K.A. `MIN_TX_SIZE` 43 | */ 44 | minimumTransactionSize = 65, 45 | /** 46 | * A.K.A. `MAX_STANDARD_TX_SIZE` 47 | */ 48 | maximumStandardTransactionSize = 100_000, 49 | /** 50 | * A.K.A. `MAX_TX_SIZE` 51 | */ 52 | maximumTransactionSize = 1_000_000, 53 | /** 54 | * A.K.A. `MAXIMUM_ELEMENT_SIZE_64_BIT` 55 | */ 56 | maximumVmNumberLength = 8, 57 | // eslint-disable-next-line @typescript-eslint/no-mixed-enums 58 | minVmNumber = '-9223372036854775807', 59 | maxVmNumber = '9223372036854775807', 60 | schnorrSignatureLength = 64, 61 | maximumCommitmentLength = 40, 62 | } 63 | 64 | // eslint-disable-next-line @typescript-eslint/naming-convention 65 | export const SigningSerializationTypesBCH2023 = [ 66 | ...SigningSerializationTypesBCH, 67 | SigningSerializationTypeBCH.allOutputsAllUtxos, 68 | SigningSerializationTypeBCH.correspondingOutputAllUtxos, 69 | SigningSerializationTypeBCH.noOutputsAllUtxos, 70 | ]; 71 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2023/bch-2023-errors.ts: -------------------------------------------------------------------------------- 1 | export enum AuthenticationErrorBCH2023 { 2 | exceededMaximumVmNumberLength = 'Program attempted an OP_BIN2NUM operation on a byte sequence that cannot be encoded within the maximum VM Number length (8 bytes).', 3 | } 4 | 5 | // eslint-disable-next-line @typescript-eslint/naming-convention 6 | export const AuthenticationErrorBCH = AuthenticationErrorBCH2023; 7 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2023/bch-2023-vm.ts: -------------------------------------------------------------------------------- 1 | import { createVirtualMachine } from '../../../virtual-machine.js'; 2 | 3 | import { createInstructionSetBCH2023 } from './bch-2023-instruction-set.js'; 4 | 5 | /** 6 | * Initialize a virtual machine using the `BCH_2023_05` instruction set. 7 | * 8 | * @param standard - If `true`, the additional `isStandard` validations will be 9 | * enabled. Transactions that fail these rules are often called "non-standard" 10 | * and can technically be included by miners in valid blocks, but most network 11 | * nodes will refuse to relay them. (Default: `true`) 12 | */ 13 | export const createVirtualMachineBCH2023 = (standard = true) => 14 | createVirtualMachine(createInstructionSetBCH2023(standard)); 15 | 16 | export const createVirtualMachineBCH = createVirtualMachineBCH2023; 17 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/2023/bch-2023.ts: -------------------------------------------------------------------------------- 1 | export * from './bch-2023-consensus.js'; 2 | export * from './bch-2023-crypto.js'; 3 | export * from './bch-2023-descriptions.js'; 4 | export * from './bch-2023-errors.js'; 5 | export * from './bch-2023-instruction-set.js'; 6 | export * from './bch-2023-opcodes.js'; 7 | export * from './bch-2023-tokens.js'; 8 | export * from './bch-2023-vm.js'; 9 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/chips/bch-chips-errors.ts: -------------------------------------------------------------------------------- 1 | export enum AuthenticationErrorBCHCHIPs { 2 | invalidBoolean = 'Invalid input: this operation requires a valid boolean (VM Number 0 or VM Number 1).', 3 | unexpectedUntil = 'Encountered an OP_UNTIL that is not following a matching OP_BEGIN.', 4 | excessiveHashing = 'Program attempted a hashing operation that would exceed the hashing limit (660 hash digest iterations).', 5 | excessiveLooping = 'Program attempted an OP_UNTIL operation that would exceed the limit of repeated bytes (10,000 bytes minus active bytecode length).', 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/chips/bch-chips-types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AuthenticationInstruction, 3 | AuthenticationProgramBCH, 4 | AuthenticationProgramCommon, 5 | AuthenticationProgramStateAlternateStack, 6 | AuthenticationProgramStateCodeSeparator, 7 | AuthenticationProgramStateControlStack, 8 | AuthenticationProgramStateError, 9 | AuthenticationProgramStateMinimum, 10 | AuthenticationProgramStateSignatureAnalysis, 11 | AuthenticationProgramStateStack, 12 | AuthenticationProgramStateTransactionContext, 13 | AuthenticationVirtualMachine, 14 | ResolvedTransactionBCH, 15 | } from '../../../../lib.js'; 16 | import { 17 | cloneAuthenticationInstruction, 18 | cloneAuthenticationProgramCommon, 19 | cloneStack, 20 | } from '../../common/common.js'; 21 | 22 | /** 23 | * Consensus settings for the `BCH_CHIPs` instruction set. 24 | */ 25 | export enum ConsensusBCHCHIPs { 26 | maximumTransactionVersion = 2, 27 | bannedTransactionSize = 64, 28 | maximumHashDigestIterations = 660, 29 | } 30 | 31 | export type AuthenticationProgramStateControlStackCHIPs = 32 | AuthenticationProgramStateControlStack; 33 | 34 | export type AuthenticationProgramStateResourceLimitsBCHCHIPs = { 35 | /** 36 | * An unsigned integer counter used by `OP_UNTIL` to prevent excessive use of 37 | * loops. 38 | */ 39 | repeatedBytes: number; 40 | /** 41 | * An unsigned integer counter use to count the total number of hash digest 42 | * iterations that required during this evaluation. 43 | */ 44 | hashDigestIterations: number; 45 | }; 46 | 47 | export type AuthenticationProgramStateBCHCHIPs = 48 | AuthenticationProgramStateAlternateStack & 49 | AuthenticationProgramStateCodeSeparator & 50 | AuthenticationProgramStateControlStackCHIPs & 51 | AuthenticationProgramStateError & 52 | AuthenticationProgramStateMinimum & 53 | AuthenticationProgramStateResourceLimitsBCHCHIPs & 54 | AuthenticationProgramStateSignatureAnalysis & 55 | AuthenticationProgramStateStack & 56 | AuthenticationProgramStateTransactionContext; 57 | 58 | export type AuthenticationVirtualMachineBCHCHIPs = AuthenticationVirtualMachine< 59 | ResolvedTransactionBCH, 60 | AuthenticationProgramBCH, 61 | AuthenticationProgramStateBCHCHIPs 62 | >; 63 | 64 | /** 65 | * @deprecated use `structuredClone` instead 66 | */ 67 | export const cloneAuthenticationProgramStateBCHCHIPs = < 68 | State extends AuthenticationProgramStateBCHCHIPs, 69 | >( 70 | state: State, 71 | ) => ({ 72 | ...(state.error === undefined ? {} : { error: state.error }), 73 | alternateStack: cloneStack(state.alternateStack), 74 | controlStack: state.controlStack.slice(), 75 | hashDigestIterations: state.hashDigestIterations, 76 | instructions: state.instructions.map(cloneAuthenticationInstruction), 77 | ip: state.ip, 78 | lastCodeSeparator: state.lastCodeSeparator, 79 | program: cloneAuthenticationProgramCommon(state.program), 80 | repeatedBytes: state.repeatedBytes, 81 | signedMessages: state.signedMessages.map((item) => ({ 82 | digest: item.digest.slice(), 83 | ...('serialization' in item 84 | ? { serialization: item.serialization.slice() } 85 | : { message: item.message.slice() }), 86 | })), 87 | stack: cloneStack(state.stack), 88 | }); 89 | 90 | export const createAuthenticationProgramStateBCHCHIPs = ({ 91 | program, 92 | instructions, 93 | stack, 94 | }: { 95 | program: AuthenticationProgramCommon; 96 | instructions: AuthenticationInstruction[]; 97 | stack: Uint8Array[]; 98 | }): AuthenticationProgramStateBCHCHIPs => ({ 99 | alternateStack: [], 100 | controlStack: [], 101 | hashDigestIterations: 0, 102 | instructions, 103 | ip: 0, 104 | lastCodeSeparator: -1, 105 | program, 106 | repeatedBytes: 0, 107 | signedMessages: [], 108 | stack, 109 | }); 110 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/chips/bch-chips-vm.ts: -------------------------------------------------------------------------------- 1 | import { createVirtualMachine } from '../../../virtual-machine.js'; 2 | 3 | import { createInstructionSetBCHCHIPs } from './bch-chips-instruction-set.js'; 4 | 5 | /** 6 | * Initialize a virtual machine using the BCH CHIPs instruction set, an 7 | * informal, speculative instruction set that implements a variety of future 8 | * Bitcoin Cash Improvement Proposals (CHIPs). 9 | * 10 | * @param standard - If `true`, the additional `isStandard` validations will be 11 | * enabled. Transactions that fail these rules are often called "non-standard" 12 | * and can technically be included by miners in valid blocks, but most network 13 | * nodes will refuse to relay them. (Default: `true`) 14 | */ 15 | export const createVirtualMachineBCHCHIPs = (standard = true) => 16 | createVirtualMachine(createInstructionSetBCHCHIPs(standard)); 17 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/bch/chips/bch-chips.ts: -------------------------------------------------------------------------------- 1 | export * from './bch-chips-crypto.js'; 2 | export * from './bch-chips-descriptions.js'; 3 | export * from './bch-chips-errors.js'; 4 | export * from './bch-chips-instruction-set.js'; 5 | export * from './bch-chips-loops.js'; 6 | export * from './bch-chips-opcodes.js'; 7 | export * from './bch-chips-types.js'; 8 | export * from './bch-chips-vm.js'; 9 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/btc/btc-types.ts: -------------------------------------------------------------------------------- 1 | import type { AuthenticationProgramStateCommon } from '../../../lib.js'; 2 | 3 | export type SegWitState = { 4 | witnessBytecode: Uint8Array; 5 | }; 6 | 7 | export type AuthenticationProgramStateBTC = AuthenticationProgramStateCommon & 8 | SegWitState; 9 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/btc/btc.ts: -------------------------------------------------------------------------------- 1 | export * from './btc-descriptions.js'; 2 | export * from './btc-opcodes.js'; 3 | export * from './btc-types.js'; 4 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/bitwise.ts: -------------------------------------------------------------------------------- 1 | import { binsAreEqual } from '../../../format/format.js'; 2 | import type { 3 | AuthenticationProgramStateError, 4 | AuthenticationProgramStateStack, 5 | Operation, 6 | } from '../../../lib.js'; 7 | 8 | import { 9 | combineOperations, 10 | pushToStack, 11 | useTwoStackItems, 12 | } from './combinators.js'; 13 | import { applyError, AuthenticationErrorCommon } from './errors.js'; 14 | import { opVerify } from './flow-control.js'; 15 | import { booleanToVmNumber } from './instruction-sets-utils.js'; 16 | 17 | export const opEqual = < 18 | State extends AuthenticationProgramStateError & 19 | AuthenticationProgramStateStack, 20 | >( 21 | state: State, 22 | ) => 23 | useTwoStackItems(state, (nextState, [element1, element2]) => 24 | pushToStack(nextState, booleanToVmNumber(binsAreEqual(element1, element2))), 25 | ); 26 | 27 | export const opEqualVerify = combineOperations(opEqual, opVerify); 28 | 29 | export const bitwiseOperation = 30 | < 31 | State extends AuthenticationProgramStateError & 32 | AuthenticationProgramStateStack, 33 | >( 34 | combine: (a: Uint8Array, b: Uint8Array) => Uint8Array, 35 | ): Operation => 36 | (state: State) => 37 | useTwoStackItems(state, (nextState, [a, b]) => 38 | a.length === b.length 39 | ? pushToStack(nextState, combine(a, b)) 40 | : applyError( 41 | nextState, 42 | AuthenticationErrorCommon.mismatchedBitwiseOperandLength, 43 | ), 44 | ); 45 | 46 | // eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion 47 | export const opAnd = bitwiseOperation((a, b) => a.map((v, i) => v & b[i]!)) as < 48 | State extends AuthenticationProgramStateError & 49 | AuthenticationProgramStateStack, 50 | >( 51 | state: State, 52 | ) => State; 53 | 54 | // eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion 55 | export const opOr = bitwiseOperation((a, b) => a.map((v, i) => v | b[i]!)) as < 56 | State extends AuthenticationProgramStateError & 57 | AuthenticationProgramStateStack, 58 | >( 59 | state: State, 60 | ) => State; 61 | 62 | // eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion 63 | export const opXor = bitwiseOperation((a, b) => a.map((v, i) => v ^ b[i]!)) as < 64 | State extends AuthenticationProgramStateError & 65 | AuthenticationProgramStateStack, 66 | >( 67 | state: State, 68 | ) => State; 69 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The "common" instruction set includes all virtual machine (VM) operations, 3 | * types, and utilities that are used by more than one Libauth-supported VM. 4 | */ 5 | export * from './arithmetic.js'; 6 | export * from './bitwise.js'; 7 | export * from './combinators.js'; 8 | export * from './common-types.js'; 9 | export * from './consensus.js'; 10 | export * from './crypto.js'; 11 | export * from './encoding.js'; 12 | export * from './errors.js'; 13 | export * from './flow-control.js'; 14 | export * from './format.js'; 15 | export * from './inspection.js'; 16 | export * from './instruction-sets-types.js'; 17 | export * from './instruction-sets-utils.js'; 18 | export * from './nop.js'; 19 | export * from './push.js'; 20 | export * from './signing-serialization.js'; 21 | export * from './stack.js'; 22 | export * from './time.js'; 23 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/consensus.ts: -------------------------------------------------------------------------------- 1 | import { SigningSerializationTypeBCH } from './signing-serialization.js'; 2 | 3 | /** 4 | * Consensus settings for the `BCH_2022_05` instruction set. 5 | */ 6 | export enum ConsensusCommon { 7 | /** 8 | * A.K.A. `MAX_SCRIPT_SIZE` 9 | */ 10 | maximumBytecodeLength = 10000, 11 | /** 12 | * A.K.A. `MAX_OP_RETURN_RELAY`, `nMaxDatacarrierBytes` 13 | */ 14 | maximumDataCarrierBytes = 223, 15 | /** 16 | * A.K.A. `MAX_OPS_PER_SCRIPT` 17 | */ 18 | maximumOperationCount = 201, 19 | /** 20 | * A.K.A. `MAX_STACK_SIZE` 21 | */ 22 | maximumStackDepth = 1000, 23 | /** 24 | * A.K.A. `MAX_SCRIPT_ELEMENT_SIZE` 25 | */ 26 | maximumStackItemLength = 520, 27 | /** 28 | * A.K.A. `MAX_STANDARD_VERSION` 29 | */ 30 | maximumStandardVersion = 2, 31 | /** 32 | * A.K.A. `MAX_TX_IN_SCRIPT_SIG_SIZE` 33 | */ 34 | maximumStandardUnlockingBytecodeLength = 1650, 35 | /** 36 | * A.K.A. `MIN_TX_SIZE` 37 | */ 38 | minimumTransactionSize = 100, 39 | /** 40 | * A.K.A. `MAX_STANDARD_TX_SIZE` 41 | */ 42 | maximumStandardTransactionSize = 100_000, 43 | /** 44 | * A.K.A. `MAX_TX_SIZE` 45 | */ 46 | maximumTransactionSize = 1_000_000, 47 | /** 48 | * A.K.A. `MAXIMUM_ELEMENT_SIZE_64_BIT` 49 | */ 50 | maximumVmNumberLength = 8, 51 | // eslint-disable-next-line @typescript-eslint/no-mixed-enums 52 | minVmNumber = '-9223372036854775807', 53 | maxVmNumber = '9223372036854775807', 54 | schnorrSignatureLength = 64, 55 | } 56 | 57 | // eslint-disable-next-line @typescript-eslint/naming-convention 58 | export const SigningSerializationTypesCommon = [ 59 | SigningSerializationTypeBCH.allOutputs, 60 | SigningSerializationTypeBCH.allOutputsSingleInput, 61 | SigningSerializationTypeBCH.correspondingOutput, 62 | SigningSerializationTypeBCH.correspondingOutputSingleInput, 63 | SigningSerializationTypeBCH.noOutputs, 64 | SigningSerializationTypeBCH.noOutputsSingleInput, 65 | ]; 66 | 67 | // eslint-disable-next-line @typescript-eslint/naming-convention 68 | export const SigningSerializationTypesBCH = SigningSerializationTypesCommon; 69 | 70 | // eslint-disable-next-line @typescript-eslint/naming-convention 71 | export const ConsensusBCH = ConsensusCommon; 72 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/flow-control.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AuthenticationProgramStateControlStack, 3 | AuthenticationProgramStateError, 4 | AuthenticationProgramStateStack, 5 | } from '../../../lib.js'; 6 | 7 | import { useOneStackItem } from './combinators.js'; 8 | import { applyError, AuthenticationErrorCommon } from './errors.js'; 9 | import { stackItemIsTruthy } from './instruction-sets-utils.js'; 10 | 11 | export const opVerify = < 12 | State extends AuthenticationProgramStateError & 13 | AuthenticationProgramStateStack, 14 | >( 15 | state: State, 16 | ) => 17 | useOneStackItem(state, (nextState, [item]) => 18 | stackItemIsTruthy(item) 19 | ? nextState 20 | : applyError(nextState, AuthenticationErrorCommon.failedVerify), 21 | ); 22 | 23 | export const reservedOperation = < 24 | State extends AuthenticationProgramStateError, 25 | >( 26 | state: State, 27 | ) => applyError(state, AuthenticationErrorCommon.calledReserved); 28 | 29 | export const opReturn = ( 30 | state: State, 31 | ) => applyError(state, AuthenticationErrorCommon.calledReturn); 32 | 33 | export const opIf = < 34 | State extends AuthenticationProgramStateControlStack & 35 | AuthenticationProgramStateError & 36 | AuthenticationProgramStateStack, 37 | >( 38 | state: State, 39 | ) => { 40 | if (state.controlStack.every((item) => item)) { 41 | return useOneStackItem(state, (nextState, [item]) => { 42 | // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data 43 | nextState.controlStack.push(stackItemIsTruthy(item)); 44 | return state; 45 | }); 46 | } 47 | // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data 48 | state.controlStack.push(false); 49 | return state; 50 | }; 51 | 52 | /** 53 | * Note, `OP_NOTIF` is not completely equivalent to `OP_NOT OP_IF`. `OP_NOT` 54 | * operates on a VM Number (as the inverse of `OP_0NOTEQUAL`), while `OP_NOTIF` 55 | * checks the "truthy-ness" of a stack item like `OP_IF`. 56 | */ 57 | export const opNotIf = < 58 | State extends AuthenticationProgramStateControlStack & 59 | AuthenticationProgramStateError & 60 | AuthenticationProgramStateStack, 61 | >( 62 | state: State, 63 | ) => { 64 | if (state.controlStack.every((item) => item)) { 65 | return useOneStackItem(state, (nextState, [item]) => { 66 | // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data 67 | nextState.controlStack.push(!stackItemIsTruthy(item)); 68 | return state; 69 | }); 70 | } 71 | // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data 72 | state.controlStack.push(false); 73 | return state; 74 | }; 75 | 76 | export const opEndIf = < 77 | State extends AuthenticationProgramStateControlStack & 78 | AuthenticationProgramStateError, 79 | >( 80 | state: State, 81 | ) => { 82 | // eslint-disable-next-line functional/immutable-data 83 | const element = state.controlStack.pop(); 84 | if (element === undefined) { 85 | return applyError(state, AuthenticationErrorCommon.unexpectedEndIf); 86 | } 87 | return state; 88 | }; 89 | 90 | export const opElse = < 91 | State extends AuthenticationProgramStateControlStack & 92 | AuthenticationProgramStateError, 93 | >( 94 | state: State, 95 | ) => { 96 | const top = state.controlStack[state.controlStack.length - 1]; 97 | if (top === undefined) { 98 | return applyError(state, AuthenticationErrorCommon.unexpectedElse); 99 | } 100 | // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data 101 | state.controlStack[state.controlStack.length - 1] = !top; 102 | return state; 103 | }; 104 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/nop.ts: -------------------------------------------------------------------------------- 1 | import type { AuthenticationProgramStateError } from '../../../lib.js'; 2 | 3 | import { applyError, AuthenticationErrorCommon } from './errors.js'; 4 | 5 | export const opNop = (state: State) => state; 6 | 7 | export const opNopDisallowed = ( 8 | state: State, 9 | ) => applyError(state, AuthenticationErrorCommon.calledUpgradableNop); 10 | 11 | /** 12 | * "Disabled" operations are explicitly forbidden from occurring anywhere in VM 13 | * bytecode, even within an unexecuted branch. 14 | */ 15 | export const disabledOperation = < 16 | State extends AuthenticationProgramStateError, 17 | >( 18 | state: State, 19 | ) => applyError(state, AuthenticationErrorCommon.unknownOpcode); 20 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/push.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { encodeDataPush, hexToBin, range } from '../../../lib.js'; 4 | 5 | const enum PushOperationConstants { 6 | OP_PUSHDATA_1 = 0x4c, 7 | OP_PUSHDATA_2 = 0x4d, 8 | OP_PUSHDATA_4 = 0x4e, 9 | /** 10 | * 256 - 1 11 | */ 12 | maximumPushData1Size = 255, 13 | /** 14 | * 256 ** 2 - 1 15 | */ 16 | maximumPushData2Size = 65535, 17 | /** 18 | * 256 ** 4 - 1 19 | */ 20 | maximumPushData4Size = 4294967295, 21 | } 22 | 23 | const prefixDataPushVectors = [ 24 | ['', '00'], 25 | ['81', '4f'], 26 | ['01', '51'], 27 | ['02', '52'], 28 | ['03', '53'], 29 | ['04', '54'], 30 | ['05', '55'], 31 | ['06', '56'], 32 | ['07', '57'], 33 | ['08', '58'], 34 | ['09', '59'], 35 | ['0a', '5a'], 36 | ['0b', '5b'], 37 | ['0c', '5c'], 38 | ['0d', '5d'], 39 | ['0e', '5e'], 40 | ['0f', '5f'], 41 | ['10', '60'], 42 | ['00', '0100'], 43 | ['0000', '020000'], 44 | ['80', '0180'], 45 | ['0081', '020081'], 46 | ['123456', '03123456'], 47 | ['123456789012345678901234567890', '0f123456789012345678901234567890'], 48 | ] as const; 49 | 50 | test('prefixDataPush', (t) => { 51 | prefixDataPushVectors.forEach(([inputHex, outputHex]) => { 52 | t.deepEqual(encodeDataPush(hexToBin(inputHex)), hexToBin(outputHex)); 53 | }); 54 | t.deepEqual( 55 | encodeDataPush( 56 | Uint8Array.from(range(PushOperationConstants.maximumPushData1Size)), 57 | ), 58 | Uint8Array.from([ 59 | PushOperationConstants.OP_PUSHDATA_1, 60 | 0xff, 61 | ...range(PushOperationConstants.maximumPushData1Size), 62 | ]), 63 | ); 64 | t.deepEqual( 65 | encodeDataPush( 66 | Uint8Array.from(range(PushOperationConstants.maximumPushData1Size + 1)), 67 | ), 68 | Uint8Array.from([ 69 | PushOperationConstants.OP_PUSHDATA_2, 70 | 0, 71 | 1, 72 | ...range(PushOperationConstants.maximumPushData1Size + 1), 73 | ]), 74 | ); 75 | t.deepEqual( 76 | encodeDataPush( 77 | Uint8Array.from(range(PushOperationConstants.maximumPushData2Size)), 78 | ), 79 | Uint8Array.from([ 80 | PushOperationConstants.OP_PUSHDATA_2, 81 | 0xff, 82 | 0xff, 83 | ...range(PushOperationConstants.maximumPushData2Size), 84 | ]), 85 | ); 86 | t.deepEqual( 87 | encodeDataPush( 88 | Uint8Array.from(range(PushOperationConstants.maximumPushData2Size + 1)), 89 | ), 90 | Uint8Array.from([ 91 | PushOperationConstants.OP_PUSHDATA_4, 92 | 0, 93 | 0, 94 | 1, 95 | 0, 96 | ...range(PushOperationConstants.maximumPushData2Size + 1), 97 | ]), 98 | ); 99 | }); 100 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/common/signing-serialization.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | binToHex, 5 | decodeTransactionUnsafeCommon, 6 | encodeSigningSerializationBCH, 7 | generateSigningSerializationComponentsBCH, 8 | hash256, 9 | hexToBin, 10 | isLegacySigningSerialization, 11 | numberToBinInt32TwosCompliment, 12 | } from '../../../lib.js'; 13 | // eslint-disable-next-line import/no-internal-modules, import/no-restricted-paths 14 | import sighashTests from '../xec/fixtures/satoshi-client/sighash.json' assert { type: 'json' }; 15 | 16 | const tests = Object.values(sighashTests) 17 | .filter((e) => e.length !== 1 && e.length < 8) 18 | .map((expectation, testIndex) => ({ 19 | inputIndex: expectation[2] as number, 20 | scriptHex: expectation[1] as string, 21 | signingSerializationBCHDigestHex: expectation[4] as string, 22 | signingSerializationType: expectation[3] as number, 23 | testIndex, 24 | transactionHex: expectation[0] as string, 25 | })) 26 | .filter( 27 | (expectation) => 28 | /** 29 | * Currently, this library only supports the new BCH signing serialization 30 | * algorithm. If the legacy algorithm is implemented, we can re-enable the 31 | * rest of these tests. 32 | */ 33 | !isLegacySigningSerialization(expectation.signingSerializationType), 34 | ); 35 | 36 | /** 37 | * Isolate a single test for debugging 38 | */ 39 | // const pendingTests = tests.filter(e => e.testIndex === 999); 40 | const pendingTests = tests; 41 | 42 | pendingTests.map((expectation, currentTest) => { 43 | test.skip(`[signing-serialization tests] sighash.json ${currentTest}/${pendingTests.length} (#${expectation.testIndex})`, (t) => { 44 | const tx = decodeTransactionUnsafeCommon( 45 | hexToBin(expectation.transactionHex), 46 | ); 47 | const lockingBytecode = hexToBin(expectation.scriptHex); 48 | 49 | const signingSerializationType = numberToBinInt32TwosCompliment( 50 | expectation.signingSerializationType, 51 | ); 52 | const sourceOutputs = []; 53 | sourceOutputs[expectation.inputIndex] = { 54 | lockingBytecode: Uint8Array.of(), 55 | valueSatoshis: 0n, 56 | }; 57 | 58 | const components = generateSigningSerializationComponentsBCH({ 59 | inputIndex: expectation.inputIndex, 60 | sourceOutputs, 61 | transaction: tx, 62 | }); 63 | const serialization = encodeSigningSerializationBCH({ 64 | correspondingOutput: components.correspondingOutput, 65 | coveredBytecode: lockingBytecode, 66 | forkId: signingSerializationType.slice(1, 4), 67 | locktime: components.locktime, 68 | outpointIndex: components.outpointIndex, 69 | outpointTransactionHash: components.outpointTransactionHash, 70 | outputTokenPrefix: components.outputTokenPrefix, 71 | outputValue: components.outputValue, 72 | sequenceNumber: components.sequenceNumber, 73 | signingSerializationType: signingSerializationType.slice(0, 1), 74 | transactionOutpoints: components.transactionOutpoints, 75 | transactionOutputs: components.transactionOutputs, 76 | transactionSequenceNumbers: components.transactionSequenceNumbers, 77 | transactionUtxos: components.transactionUtxos, 78 | version: components.version, 79 | }); 80 | const digest = hash256(serialization); 81 | t.deepEqual( 82 | digest, 83 | hexToBin(expectation.signingSerializationBCHDigestHex).reverse(), 84 | `failed serialization: ${binToHex(serialization)}`, 85 | ); 86 | }); 87 | return undefined; 88 | }); 89 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/instruction-sets.ts: -------------------------------------------------------------------------------- 1 | export * from './xec/xec.js'; 2 | export * from './bch/2022/bch-2022.js'; 3 | export * from './bch/2023/bch-2023.js'; 4 | export * from './bch/chips/bch-chips.js'; 5 | export * from './btc/btc.js'; 6 | export * from './common/common.js'; 7 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/readme.md: -------------------------------------------------------------------------------- 1 | # Libauth Instruction Sets 2 | 3 | This directory contains all of Libauth's supported instruction sets. 4 | 5 | Functionality that is shared among multiple instruction sets is typically included in the `common` directory, and VM-specific functionality is included in each respective VM directory. 6 | 7 | ## Copy, Don't Complicate 8 | 9 | **While most of Libauth attempts to be DRY (Don't Repeat Yourself), instruction sets need not follow this approach.** 10 | 11 | If an upgrade proposal requires adding a new code path within a `common` operation, **resist the urge to add flags to the operation**. Instead, copy the operation into the instruction set's directory and modify it there. 12 | 13 | Libauth instruction sets should be as clearly defined and readable as possible, ideally within a single `CODE-instruction-set.ts` file. While short-lived instruction sets may be defined by slightly extending an existing, similar instruction set (e.g. a proposed network upgrade or a close relative from a recent network split), VMs intended for long-term support in Libauth should usually have a single-file definition for readability. 14 | 15 | These single-file instruction set files should generally only import operations from sibling files and the `common` directory. 16 | 17 | ## Virtual Machine Support 18 | 19 | Libauth aims to provide support for the Virtual Machine (VM) used by every public bitcoin-like network and for public upgrade proposals with stable technical specifications. 20 | 21 | Where multiple VMs include similar functionality, **Libauth's exported utilities should prefer the configuration used by the latest version of the Bitcoin Cash (BCH) VM**. 22 | 23 | When a pending upgrade specification for BCH has wide consensus and a well-established activation date, Libauth utilities may be upgraded prior to the new specification's mainnet activation. 24 | 25 | If a proposed upgrade seems likely to cause a network split, Libauth will attempt to support both sides and may wait to select the next default VM until after the split occurs (or the disagreement is resolved). 26 | 27 | ## Virtual Machine Deprecation 28 | 29 | Generally, Libauth only maintains support for active VM versions. For example, `BCH_2019_05` was an upgrade that: 30 | 31 | - enabled Schnorr signature support in `OP_CHECKSIG` and `OP_CHECKDATASIG` 32 | - added a clean-stack exception for SegWit recovery 33 | 34 | The `BCH_2019_05` VM version was replaced without a network split by the `BCH_2019_11` upgrade, meaning `BCH_2019_05` is no longer in use by any public network. As such, relevant code paths, flags, and other VM-specific functionality for `BCH_2019_05` have been removed to simplify Libauth's code. (Of course, historical implementations will always remain available in the Git history and previously-released versions of Libauth.) 35 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/xec/fixtures/satoshi-client/bitcoin-satoshi-utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | flattenBinArray, 3 | hexToBin, 4 | utf8ToBin, 5 | } from '../../../../../format/format.js'; 6 | import { 7 | bigIntToVmNumber, 8 | encodeDataPush, 9 | generateBytecodeMap, 10 | } from '../../../common/common.js'; 11 | import { OpcodesXEC } from '../../xec-opcodes.js'; 12 | 13 | export const bitcoinSatoshiOpcodes = Object.entries( 14 | generateBytecodeMap(OpcodesXEC), 15 | ).reduce<{ [opcode: string]: Uint8Array }>( 16 | (acc, cur) => ({ ...acc, [cur[0].slice('OP_'.length)]: cur[1] }), 17 | { 18 | PUSHDATA1: Uint8Array.of(OpcodesXEC.OP_PUSHDATA_1), // eslint-disable-line @typescript-eslint/naming-convention 19 | PUSHDATA2: Uint8Array.of(OpcodesXEC.OP_PUSHDATA_2), // eslint-disable-line @typescript-eslint/naming-convention 20 | PUSHDATA4: Uint8Array.of(OpcodesXEC.OP_PUSHDATA_4), // eslint-disable-line @typescript-eslint/naming-convention 21 | }, 22 | ); 23 | 24 | /** 25 | * Convert a string from the Satoshi implementation's `script_tests.json` 26 | * text-format to bytecode. The string must be valid – this method attempts to 27 | * convert all unmatched tokens to `BigInt`s. 28 | * 29 | * @privateRemarks 30 | * This method doesn't use {@link compileScript} because of a slight 31 | * incompatibility in the languages. In CashAssembly, BigIntLiterals are a 32 | * primitive type, and must be surrounded by a push statement (e.g. `<100>`) to 33 | * push a number to the stack. In the `script_tests.json` text-format, numbers 34 | * are assumed to be pushed. We could implement a transformation after the 35 | * compiler's parse step, but because this format doesn't require any other 36 | * features of the compiler, we opt to implement this as a simple method. 37 | * @param satoshiScript - the script in the Satoshi implementation's 38 | * `script_tests.json` text format 39 | */ 40 | export const assembleBitcoinSatoshiScript = (satoshiScript: string) => 41 | flattenBinArray( 42 | satoshiScript 43 | .split(' ') 44 | .filter((token) => token !== '') 45 | .map((token) => 46 | token.startsWith('0x') 47 | ? hexToBin(token.slice('0x'.length)) 48 | : token.startsWith("'") 49 | ? encodeDataPush(utf8ToBin(token.slice(1, token.length - 1))) 50 | : bitcoinSatoshiOpcodes[token] ?? 51 | encodeDataPush(bigIntToVmNumber(BigInt(token))), 52 | ), 53 | ); 54 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/xec/xec-types.ts: -------------------------------------------------------------------------------- 1 | export enum ConsensusXEC { 2 | /** 3 | * A.K.A. `MAX_SCRIPT_ELEMENT_SIZE` 4 | */ 5 | maximumStackItemLength = 520, 6 | maximumVmNumberLength = 4, 7 | /** 8 | * A.K.A. `MAX_OPS_PER_SCRIPT` 9 | */ 10 | maximumOperationCount = 201, 11 | /** 12 | * A.K.A. `MAX_SCRIPT_SIZE` 13 | */ 14 | maximumBytecodeLength = 10000, 15 | /** 16 | * A.K.A. `MAX_STACK_SIZE` 17 | */ 18 | maximumStackDepth = 1000, 19 | schnorrSignatureLength = 64, 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/xec/xec-vm.ts: -------------------------------------------------------------------------------- 1 | import { createVirtualMachine } from '../../virtual-machine.js'; 2 | 3 | import { createInstructionSetXEC } from './xec-instruction-set.js'; 4 | 5 | /** 6 | * Initialize a virtual machine using the XEC instruction set. 7 | * 8 | * @param standard - If `true`, the additional `isStandard` validations will be 9 | * enabled. Transactions that fail these rules are often called "non-standard" 10 | * and can technically be included by miners in valid blocks, but most network 11 | * nodes will refuse to relay them. (Default: `true`) 12 | */ 13 | export const createVirtualMachineXEC = (standard = true) => 14 | createVirtualMachine(createInstructionSetXEC(standard)); 15 | -------------------------------------------------------------------------------- /src/lib/vm/instruction-sets/xec/xec.ts: -------------------------------------------------------------------------------- 1 | export * from './xec-descriptions.js'; 2 | export * from './xec-instruction-set.js'; 3 | export * from './xec-opcodes.js'; 4 | export * from './xec-types.js'; 5 | export * from './xec-vm.js'; 6 | export * from './fixtures/satoshi-client/bitcoin-satoshi-utils.js'; 7 | -------------------------------------------------------------------------------- /src/lib/vm/vm.ts: -------------------------------------------------------------------------------- 1 | export * from './instruction-sets/instruction-sets.js'; 2 | export * from './virtual-machine.js'; 3 | export * from './vm-types.js'; 4 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/bch-vmb-test-mixins.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | WalletTemplateScenario, 3 | WalletTemplateScenarioInput, 4 | WalletTemplateScenarioSourceOutput, 5 | WalletTemplateScenarioTransactionOutput, 6 | } from '../lib.js'; 7 | 8 | export const simpleP2pkhOutput: WalletTemplateScenarioSourceOutput = { 9 | lockingBytecode: { script: 'lockP2pkh' }, 10 | valueSatoshis: 10_000, 11 | }; 12 | export const simpleP2pkhInput: WalletTemplateScenarioInput = { 13 | unlockingBytecode: { script: 'unlockP2pkh' }, 14 | }; 15 | export const emptyP2sh20Output: WalletTemplateScenarioSourceOutput = { 16 | lockingBytecode: { script: 'lockEmptyP2sh20' }, 17 | valueSatoshis: 10_000, 18 | }; 19 | export const emptyP2sh20Input: WalletTemplateScenarioInput = { 20 | unlockingBytecode: { script: 'unlockEmptyP2sh20' }, 21 | }; 22 | 23 | export const vmbTestOutput: WalletTemplateScenarioTransactionOutput = { 24 | lockingBytecode: { script: 'vmbTestNullData' }, 25 | valueSatoshis: 0, 26 | }; 27 | 28 | export const slotOutput: WalletTemplateScenarioSourceOutput = { 29 | lockingBytecode: ['slot'], 30 | valueSatoshis: 10_000, 31 | }; 32 | 33 | export const slotInput: WalletTemplateScenarioInput = { 34 | unlockingBytecode: ['slot'], 35 | }; 36 | 37 | export const slot0Scenario: WalletTemplateScenario = { 38 | sourceOutputs: [slotOutput, simpleP2pkhOutput], 39 | transaction: { 40 | inputs: [slotInput, simpleP2pkhInput], 41 | outputs: [vmbTestOutput], 42 | }, 43 | }; 44 | export const slot1Scenario: WalletTemplateScenario = { 45 | sourceOutputs: [simpleP2pkhOutput, slotOutput], 46 | transaction: { 47 | inputs: [simpleP2pkhInput, slotInput], 48 | outputs: [vmbTestOutput], 49 | }, 50 | }; 51 | export const slot2Scenario: WalletTemplateScenario = { 52 | sourceOutputs: [simpleP2pkhOutput, simpleP2pkhOutput, slotOutput], 53 | transaction: { 54 | inputs: [simpleP2pkhInput, simpleP2pkhInput, slotInput], 55 | outputs: [vmbTestOutput], 56 | }, 57 | }; 58 | export const slot9Scenario: WalletTemplateScenario = { 59 | sourceOutputs: [ 60 | simpleP2pkhOutput, 61 | simpleP2pkhOutput, 62 | simpleP2pkhOutput, 63 | simpleP2pkhOutput, 64 | simpleP2pkhOutput, 65 | simpleP2pkhOutput, 66 | simpleP2pkhOutput, 67 | simpleP2pkhOutput, 68 | simpleP2pkhOutput, 69 | slotOutput, 70 | ], 71 | transaction: { 72 | inputs: [ 73 | simpleP2pkhInput, 74 | simpleP2pkhInput, 75 | simpleP2pkhInput, 76 | simpleP2pkhInput, 77 | simpleP2pkhInput, 78 | simpleP2pkhInput, 79 | simpleP2pkhInput, 80 | simpleP2pkhInput, 81 | simpleP2pkhInput, 82 | slotInput, 83 | ], 84 | outputs: [vmbTestOutput], 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/bch-vmb-test-utils.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { stringifyTestVector, vmbTestGroupToVmbTests } from '../lib.js'; 4 | 5 | /* spell-checker: disable */ 6 | test('vmbTestGroupToVmbTests', (t) => { 7 | const result = vmbTestGroupToVmbTests([ 8 | 'Test set', 9 | [ 10 | [ 11 | 'OP_0', 12 | 'OP_SIZE <0> OP_EQUAL', 13 | 'OP_0 (A.K.A. OP_PUSHBYTES_0, OP_FALSE): zero is represented by an empty stack item', 14 | ], 15 | [ 16 | 'OP_PUSHBYTES_1', 17 | 'OP_SIZE <1> OP_EQUAL', 18 | 'OP_PUSHBYTES_1 with missing bytes', 19 | ['invalid', 'p2sh_ignore'], 20 | ], 21 | ], 22 | ]); 23 | t.deepEqual( 24 | result, 25 | [ 26 | [ 27 | [ 28 | 'lqhcn', 29 | 'Test set: OP_0 (A.K.A. OP_PUSHBYTES_0, OP_FALSE): zero is represented by an empty stack item (nonP2SH)', 30 | 'OP_0', 31 | 'OP_SIZE <0> OP_EQUAL', 32 | '020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e785000000000100000000000000000000000000000000000000000000000000000000000000010000000100000000000100000000000000000a6a08766d625f7465737400000000', 33 | '0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000003820087', 34 | ['2022_nonstandard'], 35 | 1, 36 | ], 37 | [ 38 | 'y0ql2', 39 | 'Test set: OP_0 (A.K.A. OP_PUSHBYTES_0, OP_FALSE): zero is represented by an empty stack item (P2SH20)', 40 | 'OP_0', 41 | 'OP_SIZE <0> OP_EQUAL', 42 | '020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e78500000000010000000000000000000000000000000000000000000000000000000000000001000000050003820087000000000100000000000000000a6a08766d625f7465737400000000', 43 | '0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000017a9146b14122b4b3cb280c9ec66f8e2827cf3384010a387', 44 | ['2022_standard'], 45 | 1, 46 | ], 47 | ], 48 | [ 49 | [ 50 | '7j2u2', 51 | 'Test set: OP_PUSHBYTES_1 with missing bytes (nonP2SH)', 52 | 'OP_PUSHBYTES_1', 53 | 'OP_SIZE <1> OP_EQUAL', 54 | '020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e785000000000100000000000000000000000000000000000000000000000000000000000000010000000101000000000100000000000000000a6a08766d625f7465737400000000', 55 | '0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000003825187', 56 | ['2022_invalid'], 57 | 1, 58 | ], 59 | ], 60 | ], 61 | 62 | `Stringified test vector:\n${stringifyTestVector(result)}`, 63 | ); 64 | }); 65 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/bch-vmb-tests.spec.helper.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | 3 | /** 4 | * This script generates all bch_vmb_tests, run it with: `yarn gen:vmb-tests`. 5 | */ 6 | import { writeFileSync } from 'node:fs'; 7 | import { resolve } from 'node:path'; 8 | 9 | import { vmbTestPartitionMasterTestList } from './bch-vmb-test-utils.js'; 10 | import { vmbTestsBCH } from './bch-vmb-tests.js'; 11 | 12 | /** 13 | * Script accepts one argument: an `outputDir` to which all generated files will 14 | * be saved. 15 | */ 16 | const [, , outputDir] = process.argv; 17 | if (outputDir === undefined) { 18 | // eslint-disable-next-line functional/no-throw-statements 19 | throw new Error('Script requires an output directory.'); 20 | } 21 | const outputAbsolutePath = resolve(outputDir); 22 | 23 | const testGroupsAndTypes = 2; 24 | const allTestCases = vmbTestsBCH.flat(testGroupsAndTypes); 25 | writeFileSync( 26 | `${outputAbsolutePath}/bch_vmb_tests.json`, 27 | JSON.stringify(allTestCases), 28 | { encoding: 'utf8' }, 29 | ); 30 | const partitionedTestCases = vmbTestPartitionMasterTestList(allTestCases); 31 | 32 | // eslint-disable-next-line functional/no-return-void 33 | Object.entries(partitionedTestCases).forEach(([testSetName, testSet]) => { 34 | const filepath = testSetName.includes('chip') 35 | ? `${outputAbsolutePath}/CHIPs/bch_vmb_tests_${testSetName}.json` 36 | : `${outputAbsolutePath}/bch_vmb_tests_${testSetName}.json`; 37 | writeFileSync(filepath, JSON.stringify(testSet), { encoding: 'utf8' }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/generated/bch/CHIPs/bch_vmb_tests_chip_loops_invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["ldd4d", "Conditionally executed operations: OP_BEGIN/OP_UNTIL fail on infinite loops (nonP2SH)", "<1>", "OP_IF OP_BEGIN <0> OP_UNTIL OP_ENDIF <1>", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e785000000000100000000000000000000000000000000000000000000000000000000000000010000000151000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000006636500666851", 1], 3 | ["szft5", "Conditionally executed operations: OP_BEGIN/OP_UNTIL fail on infinite loops (P2SH20)", "<1>", "OP_IF OP_BEGIN <0> OP_UNTIL OP_ENDIF <1>", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e78500000000010000000000000000000000000000000000000000000000000000000000000001000000085106636500666851000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000017a914812d396aba3175919905cd766c5875cbe076182c87", 1], 4 | ["m30e8", "CHIP-2021-05-loops: infinite loops fail after exhausting repeated bytes limit (nonP2SH)", "<1> <0>", "OP_BEGIN OP_DUP OP_UNTIL OP_DROP", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e78500000000010000000000000000000000000000000000000000000000000000000000000001000000025100000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac10270000000000000465766675", 1], 5 | ["06ty9", "CHIP-2021-05-loops: infinite loops fail after exhausting repeated bytes limit (P2SH20)", "<1> <0>", "OP_BEGIN OP_DUP OP_UNTIL OP_DROP", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e785000000000100000000000000000000000000000000000000000000000000000000000000010000000751000465766675000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000017a914b1a0d396c749da19214e584416f727d91dc1d33987", 1] 6 | ] 7 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/generated/bch/CHIPs/bch_vmb_tests_chip_loops_invalid_reasons.json: -------------------------------------------------------------------------------- 1 | { "ldd4d": "Error in evaluating input index 1: Program attempted an OP_UNTIL operation that would exceed the limit of repeated bytes (10,000 bytes minus active bytecode length). Repeated bytes: 9996; active bytecode length: 6", "szft5": "Error in evaluating input index 1: Program attempted an OP_UNTIL operation that would exceed the limit of repeated bytes (10,000 bytes minus active bytecode length). Repeated bytes: 9996; active bytecode length: 6", "m30e8": "Error in evaluating input index 1: Program attempted an OP_UNTIL operation that would exceed the limit of repeated bytes (10,000 bytes minus active bytecode length). Repeated bytes: 9998; active bytecode length: 4", "06ty9": "Error in evaluating input index 1: Program attempted an OP_UNTIL operation that would exceed the limit of repeated bytes (10,000 bytes minus active bytecode length). Repeated bytes: 9998; active bytecode length: 4" } 2 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/generated/bch/CHIPs/bch_vmb_tests_chip_loops_nonstandard.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["7y55w", "Conditionally executed operations: OP_BEGIN/OP_UNTIL are conditionally executed (nonP2SH)", "<0>", "OP_IF OP_BEGIN <0> OP_UNTIL OP_ENDIF <1>", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e785000000000100000000000000000000000000000000000000000000000000000000000000010000000100000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000006636500666851", 1], 3 | ["pwran", "CHIP-2021-05-loops: loop until the first 0x01 (nonP2SH)", "<1> <1>", "OP_BEGIN OP_DUP OP_UNTIL OP_DROP", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e78500000000010000000000000000000000000000000000000000000000000000000000000001000000025151000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac10270000000000000465766675", 1] 4 | ] 5 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/generated/bch/CHIPs/bch_vmb_tests_chip_loops_nonstandard_reasons.json: -------------------------------------------------------------------------------- 1 | { "7y55w": "Standard transactions may only spend standard output types, but source output 1 is non-standard.", "pwran": "Standard transactions may only spend standard output types, but source output 1 is non-standard." } 2 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/generated/bch/CHIPs/bch_vmb_tests_chip_loops_standard.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["27tjf", "Conditionally executed operations: OP_BEGIN/OP_UNTIL are conditionally executed (P2SH20)", "<0>", "OP_IF OP_BEGIN <0> OP_UNTIL OP_ENDIF <1>", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e78500000000010000000000000000000000000000000000000000000000000000000000000001000000080006636500666851000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000017a914812d396aba3175919905cd766c5875cbe076182c87", 1], 3 | ["ll48n", "CHIP-2021-05-loops: loop until the first 0x01 (P2SH20)", "<1> <1>", "OP_BEGIN OP_DUP OP_UNTIL OP_DROP", "020000000201000000000000000000000000000000000000000000000000000000000000000000000064417dfb529d352908ee0a88a0074c216b09793d6aa8c94c7640bb4ced51eaefc75d0aef61f7685d0307491e2628da3d4f91e86329265a4a58ca27a41ec0b8910779c32103a524f43d6166ad3567f18b0a5c769c6ab4dc02149f4d5095ccf4e8ffa293e785000000000100000000000000000000000000000000000000000000000000000000000000010000000751510465766675000000000100000000000000000a6a08766d625f7465737400000000", "0210270000000000001976a91460011c6bf3f1dd98cff576437b9d85de780f497488ac102700000000000017a914b1a0d396c749da19214e584416f727d91dc1d33987", 1] 4 | ] 5 | -------------------------------------------------------------------------------- /src/lib/vmb-tests/vmb-tests.ts: -------------------------------------------------------------------------------- 1 | export * from './bch-vmb-test-utils.js'; 2 | -------------------------------------------------------------------------------- /src/types/bitcore-lib-cash.d.ts: -------------------------------------------------------------------------------- 1 | // used in tests and benchmarks 2 | declare module 'bitcore-lib-cash' { 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | const whatever: any; 5 | // eslint-disable-next-line import/no-default-export 6 | export default whatever; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/chuhai.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-return-void */ 2 | declare module 'chuhai' { 3 | function suite(implementation: (s: Helper) => void): Promise; 4 | function suite( 5 | name: string, 6 | implementation: (s: Helper) => void, 7 | ): Promise; 8 | // eslint-disable-next-line import/no-default-export 9 | export default suite; 10 | } 11 | 12 | type Helper = { 13 | cycle: (implementation: () => void) => void; 14 | bench: ( 15 | name: string, 16 | implementation: Benchmark, 17 | opts?: BenchmarkOptions, 18 | ) => void; 19 | burn: ( 20 | name: string, 21 | implementation: Benchmark, 22 | opts?: BenchmarkOptions, 23 | ) => void; 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | set: (key: string, value: any) => void; 26 | }; 27 | 28 | type Benchmark = (deferred: { resolve: () => void }) => void; 29 | 30 | type BenchmarkOptions = { 31 | async?: boolean; 32 | defer?: boolean; 33 | }; 34 | -------------------------------------------------------------------------------- /src/types/rollup-plugin-alias.d.ts: -------------------------------------------------------------------------------- 1 | // used in benchmarks 2 | declare module '@rollup/plugin-alias' { 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | function alias(config?: RollupPluginAliasOptions): any; 5 | // eslint-disable-next-line import/no-default-export 6 | export default alias; 7 | } 8 | 9 | type RollupPluginAliasOptions = { 10 | entries: { [key: string]: string }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/types/rollup-plugin-common-js.d.ts: -------------------------------------------------------------------------------- 1 | // used in benchmarks 2 | declare module '@rollup/plugin-commonjs' { 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | function alias(): any; 5 | // eslint-disable-next-line import/no-default-export 6 | export default alias; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/secp256k1.d.ts: -------------------------------------------------------------------------------- 1 | // used in tests and benchmarks 2 | declare module 'secp256k1' { 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | const whatever: any; 5 | // eslint-disable-next-line import/no-default-export 6 | export default whatever; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "nodenext", 5 | "moduleResolution": "nodenext", 6 | "rootDir": "src", 7 | "outDir": "build", 8 | "incremental": true, 9 | "tsBuildInfoFile": "./build/.tsbuildinfo", 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 14 | "resolveJsonModule": true, 15 | "allowJs": true /* ajv validators are pre-compiled to JS */, 16 | "strict": true /* Enable all strict type-checking options. */, 17 | /* Additional Checks */ 18 | "noUnusedLocals": true /* Report errors on unused locals. */, 19 | "noUnusedParameters": true /* Report errors on unused parameters. */, 20 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 21 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 22 | // "exactOptionalPropertyTypes": true /* Enforce difference between `undefined` and optional properties */, // TODO: enable exactOptionalPropertyTypes, possibly waiting on https://github.com/microsoft/TypeScript/issues/46969#issuecomment-984083995 (or a `RequiredExactOptional` to match `PartialExactOptional`) 23 | "noUncheckedIndexedAccess": true /* Require checking that an indexed access is not undefined */, 24 | "noPropertyAccessFromIndexSignature": true /* Disallow dot syntax for fields which are not defined */, 25 | /* Debugging Options */ 26 | "traceResolution": false /* Report module resolution log messages. */, 27 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 28 | "listFiles": false /* Print names of files part of the compilation. */, 29 | "pretty": true /* Stylize errors and messages using color and context. */, 30 | "typeRoots": ["node_modules/@types", "src/types"] 31 | }, 32 | "include": ["src/**/*"], 33 | "exclude": ["node_modules/**"], 34 | "compileOnSave": false 35 | } 36 | -------------------------------------------------------------------------------- /wasm/docker/hashes.Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM liuchong/rustup:nightly 2 | FROM liuchong/rustup@sha256:57795147db06a7ebad574469fb1198cf36fc26dc74d504d128ae2160271b2b61 3 | RUN rustup target add wasm32-unknown-unknown --toolchain nightly 4 | RUN cargo +nightly install wasm-bindgen-cli 5 | RUN cargo install wasm-pack 6 | RUN apt-get update && apt-get install git cmake python -y 7 | 8 | WORKDIR / 9 | # Build Binaryen v1.38.6 10 | RUN BINARYEN_RELEASE=aab16136b9752eb0586b3a5b32b1d0e7a5803835 \ 11 | && git clone --single-branch https://github.com/WebAssembly/binaryen.git \ 12 | && cd binaryen \ 13 | && git checkout $BINARYEN_RELEASE \ 14 | && cmake . && make 15 | 16 | # ripemd160 17 | COPY wasm/hashes/ripemd160 /libauth/wasm/hashes/ripemd160 18 | WORKDIR /libauth/wasm/hashes/ripemd160 19 | RUN mkdir -p out/ripemd160 20 | RUN wasm-pack init 21 | RUN /binaryen/bin/wasm-opt -O3 pkg/ripemd160_bg.wasm -o pkg/ripemd160.wasm 22 | RUN cp pkg/ripemd160.wasm out/ripemd160 23 | RUN cp pkg/ripemd160.d.ts out/ripemd160 24 | RUN cp pkg/ripemd160.js out/ripemd160 25 | RUN OUTPUT_TS_FILE=out/ripemd160/ripemd160.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const ripemd160Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/ripemd160.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE 26 | RUN cp -r /libauth/wasm/hashes/ripemd160/out/ripemd160 /libauth/bin 27 | 28 | # sha256 29 | COPY wasm/hashes/sha256 /libauth/wasm/hashes/sha256 30 | WORKDIR /libauth/wasm/hashes/sha256 31 | RUN mkdir -p out/sha256 32 | RUN wasm-pack init 33 | RUN /binaryen/bin/wasm-opt -O3 pkg/sha256_bg.wasm -o pkg/sha256.wasm 34 | RUN cp pkg/sha256.wasm out/sha256 35 | RUN cp pkg/sha256.d.ts out/sha256 36 | RUN cp pkg/sha256.js out/sha256 37 | RUN OUTPUT_TS_FILE=out/sha256/sha256.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha256Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/sha256.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE 38 | RUN cp -r /libauth/wasm/hashes/sha256/out/sha256 /libauth/bin 39 | 40 | # sha512 41 | COPY wasm/hashes/sha512 /libauth/wasm/hashes/sha512 42 | WORKDIR /libauth/wasm/hashes/sha512 43 | RUN mkdir -p out/sha512 44 | RUN wasm-pack init 45 | RUN /binaryen/bin/wasm-opt -O3 pkg/sha512_bg.wasm -o pkg/sha512.wasm 46 | RUN cp pkg/sha512.wasm out/sha512 47 | RUN cp pkg/sha512.d.ts out/sha512 48 | RUN cp pkg/sha512.js out/sha512 49 | RUN OUTPUT_TS_FILE=out/sha512/sha512.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha512Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/sha512.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE 50 | RUN cp -r /libauth/wasm/hashes/sha512/out/sha512 /libauth/bin 51 | 52 | # sha1 53 | COPY wasm/hashes/sha1 /libauth/wasm/hashes/sha1 54 | WORKDIR /libauth/wasm/hashes/sha1 55 | RUN mkdir -p out/sha1 56 | RUN wasm-pack init 57 | RUN /binaryen/bin/wasm-opt -O3 pkg/sha1_bg.wasm -o pkg/sha1.wasm 58 | RUN cp pkg/sha1.wasm out/sha1 59 | RUN cp pkg/sha1.d.ts out/sha1 60 | RUN cp pkg/sha1.js out/sha1 61 | RUN OUTPUT_TS_FILE=out/sha1/sha1.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const sha1Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 pkg/sha1.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE 62 | RUN cp -r /libauth/wasm/hashes/sha1/out/sha1 /libauth/bin 63 | 64 | WORKDIR /libauth/wasm/hashes/ 65 | 66 | # copy outputs to mounted volume 67 | CMD ["cp", "-r", "/libauth/bin", "/libauth/out"] -------------------------------------------------------------------------------- /wasm/docker/secp256k1.Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM trzeci/emscripten-slim:latest 2 | FROM trzeci/emscripten-slim@sha256:e3cd9edf81c5d9cd78d2edf034ce6fcb2dccb35f1f5451e8ce75e5210bbbf036 3 | 4 | RUN apt-get update \ 5 | && apt-get install -y \ 6 | autoconf \ 7 | libtool \ 8 | build-essential 9 | 10 | COPY wasm /libauth/wasm 11 | 12 | WORKDIR /libauth/wasm/secp256k1 13 | 14 | RUN ./autogen.sh 15 | RUN emconfigure ./configure --enable-module-recovery \ 16 | # uncomment next line for debug build: 17 | # CFLAGS="-g -O0" 18 | # uncomment next line for production build: 19 | CFLAGS="-O3" 20 | RUN emmake make FORMAT=wasm 21 | RUN mkdir -p out/secp256k1 22 | 23 | RUN emcc src/libsecp256k1_la-secp256k1.o \ 24 | # uncomment next line for debug build: 25 | # -O0 -g4 -s ASSERTIONS=2 --source-map-base ../../../wasm/secp256k1 \ 26 | # uncomment next line for production build: 27 | -O3 \ 28 | -s WASM=1 \ 29 | -s "BINARYEN_METHOD='native-wasm'" \ 30 | -s NO_EXIT_RUNTIME=1 \ 31 | -s DETERMINISTIC=1 \ 32 | -s EXPORTED_FUNCTIONS='[ \ 33 | "_malloc", \ 34 | "_free", \ 35 | "_secp256k1_context_create", \ 36 | "_secp256k1_context_randomize", \ 37 | "_secp256k1_ec_seckey_verify", \ 38 | "_secp256k1_ec_privkey_tweak_add", \ 39 | "_secp256k1_ec_privkey_tweak_mul", \ 40 | "_secp256k1_ec_pubkey_create", \ 41 | "_secp256k1_ec_pubkey_parse", \ 42 | "_secp256k1_ec_pubkey_serialize", \ 43 | "_secp256k1_ec_pubkey_tweak_add", \ 44 | "_secp256k1_ec_pubkey_tweak_mul", \ 45 | "_secp256k1_ecdsa_recover", \ 46 | "_secp256k1_ecdsa_recoverable_signature_serialize_compact", \ 47 | "_secp256k1_ecdsa_recoverable_signature_parse_compact", \ 48 | "_secp256k1_ecdsa_sign", \ 49 | "_secp256k1_ecdsa_signature_malleate", \ 50 | "_secp256k1_ecdsa_signature_normalize", \ 51 | "_secp256k1_ecdsa_signature_parse_der", \ 52 | "_secp256k1_ecdsa_signature_parse_compact", \ 53 | "_secp256k1_ecdsa_signature_serialize_der", \ 54 | "_secp256k1_ecdsa_signature_serialize_compact", \ 55 | "_secp256k1_ecdsa_sign_recoverable", \ 56 | "_secp256k1_ecdsa_verify", \ 57 | "_secp256k1_schnorr_sign", \ 58 | "_secp256k1_schnorr_verify" \ 59 | ]' \ 60 | -o out/secp256k1/secp256k1.js 61 | 62 | RUN OUTPUT_TS_FILE=out/secp256k1/secp256k1.base64.ts; printf "/**\n * @hidden\n */\n// prettier-ignore\nexport const secp256k1Base64Bytes =\n '" > $OUTPUT_TS_FILE && base64 -w 0 out/secp256k1/secp256k1.wasm >> $OUTPUT_TS_FILE && printf "';\n" >> $OUTPUT_TS_FILE 63 | 64 | RUN cp -r /libauth/wasm/secp256k1/out /libauth/bin 65 | 66 | # copy outputs to mounted volume 67 | CMD ["cp", "-r", "/libauth/bin", "/libauth/out"] -------------------------------------------------------------------------------- /wasm/hashes/ripemd160/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | pkg -------------------------------------------------------------------------------- /wasm/hashes/ripemd160/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ripemd160" 3 | version = "0.1.0" 4 | authors = ["Jason Dreyzehner "] 5 | description = "Ripemd160 for Libauth" 6 | license = "MIT" 7 | repository = "https://github.com/bitauth/libauth/" 8 | publish = false 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | arrayref = '0.3.4' 15 | ripemd160 = '0.7.0' 16 | wasm-bindgen = '0.2.11' 17 | 18 | [profile.release] 19 | panic = 'abort' 20 | debug = false 21 | lto = true 22 | opt-level = 'z' 23 | -------------------------------------------------------------------------------- /wasm/hashes/ripemd160/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro, wasm_import_module, wasm_custom_section)] 2 | 3 | extern crate ripemd160; 4 | extern crate wasm_bindgen; 5 | #[macro_use] 6 | extern crate arrayref; 7 | 8 | use ripemd160::{Digest, Ripemd160}; 9 | use wasm_bindgen::prelude::*; 10 | 11 | #[wasm_bindgen] 12 | pub fn ripemd160(input: &[u8]) -> Vec { 13 | return Ripemd160::digest(input).to_vec(); 14 | } 15 | 16 | #[test] 17 | fn ripemd160_hash() { 18 | let hash_abc = vec![ 19 | 142, 178, 8, 247, 224, 93, 152, 122, 155, 4, 74, 142, 152, 198, 176, 135, 241, 90, 11, 252, 20 | ]; 21 | assert_eq!(ripemd160(b"abc"), hash_abc); 22 | let hash_test = vec![ 23 | 94, 82, 254, 228, 126, 107, 7, 5, 101, 247, 67, 114, 70, 140, 220, 105, 157, 232, 145, 7, 24 | ]; 25 | assert_eq!(ripemd160(b"test"), hash_test); 26 | let hash_bitcoin_ts = vec![ 27 | 114, 23, 190, 127, 93, 117, 57, 29, 77, 27, 233, 75, 218, 102, 121, 213, 45, 101, 210, 199, 28 | ]; 29 | assert_eq!(ripemd160(b"bitcoin-ts"), hash_bitcoin_ts); 30 | } 31 | 32 | const RIPEMD160_SIZE: usize = std::mem::size_of::(); 33 | 34 | #[wasm_bindgen] 35 | pub fn ripemd160_init() -> Vec { 36 | let hasher = Ripemd160::new(); 37 | let raw_state: [u8; RIPEMD160_SIZE] = unsafe { std::mem::transmute(hasher) }; 38 | raw_state.to_vec() 39 | } 40 | 41 | #[wasm_bindgen] 42 | pub fn ripemd160_update(raw_state: &mut [u8], input: &[u8]) -> Vec { 43 | let raw_state2 = array_mut_ref!(raw_state, 0, RIPEMD160_SIZE); 44 | let mut hasher: Ripemd160 = unsafe { std::mem::transmute(*raw_state2) }; 45 | hasher.input(input); 46 | let raw_state3: [u8; RIPEMD160_SIZE] = unsafe { std::mem::transmute(hasher) }; 47 | raw_state3.to_vec() 48 | } 49 | 50 | #[wasm_bindgen] 51 | pub fn ripemd160_final(raw_state: &mut [u8]) -> Vec { 52 | let raw_state2 = array_mut_ref!(raw_state, 0, RIPEMD160_SIZE); 53 | let hasher: Ripemd160 = unsafe { std::mem::transmute(*raw_state2) }; 54 | hasher.result().to_vec() 55 | } 56 | 57 | #[test] 58 | fn ripemd160_() { 59 | let hash_bitcoin_ts = vec![ 60 | 114, 23, 190, 127, 93, 117, 57, 29, 77, 27, 233, 75, 218, 102, 121, 213, 45, 101, 210, 199, 61 | ]; 62 | let mut state = ripemd160_init(); 63 | let mut state = ripemd160_update(state.as_mut_slice(), b"bitcoin"); 64 | let mut state = ripemd160_update(state.as_mut_slice(), b"-ts"); 65 | assert_eq!(ripemd160_final(state.as_mut_slice()), hash_bitcoin_ts); 66 | } 67 | -------------------------------------------------------------------------------- /wasm/hashes/sha1/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | pkg -------------------------------------------------------------------------------- /wasm/hashes/sha1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha1" 3 | version = "0.1.0" 4 | authors = ["Jason Dreyzehner "] 5 | description = "Sha1 for Libauth" 6 | license = "MIT" 7 | repository = "https://github.com/bitauth/libauth/" 8 | publish = false 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | arrayref = '0.3.4' 15 | sha-1 = '0.7.0' 16 | wasm-bindgen = '0.2.11' 17 | 18 | [profile.release] 19 | panic = 'abort' 20 | debug = false 21 | lto = true 22 | opt-level = 'z' 23 | -------------------------------------------------------------------------------- /wasm/hashes/sha1/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro, wasm_import_module, wasm_custom_section)] 2 | 3 | extern crate sha1; 4 | extern crate wasm_bindgen; 5 | #[macro_use] 6 | extern crate arrayref; 7 | 8 | use sha1::{Digest, Sha1}; 9 | use wasm_bindgen::prelude::*; 10 | 11 | #[wasm_bindgen] 12 | pub fn sha1(input: &[u8]) -> Vec { 13 | return Sha1::digest(input).to_vec(); 14 | } 15 | 16 | #[test] 17 | fn sha1_hash() { 18 | let hash_abc = vec![ 19 | 169, 153, 62, 54, 71, 6, 129, 106, 186, 62, 37, 113, 120, 80, 194, 108, 156, 208, 216, 157, 20 | ]; 21 | assert_eq!(sha1(b"abc"), hash_abc); 22 | let hash_test = vec![ 23 | 169, 74, 143, 229, 204, 177, 155, 166, 28, 76, 8, 115, 211, 145, 233, 135, 152, 47, 187, 24 | 211, 25 | ]; 26 | assert_eq!(sha1(b"test"), hash_test); 27 | let hash_bitcoin_ts = vec![ 28 | 172, 243, 119, 55, 165, 187, 137, 56, 129, 102, 231, 172, 37, 23, 43, 80, 241, 124, 241, 29 | 186, 30 | ]; 31 | assert_eq!(sha1(b"bitcoin-ts"), hash_bitcoin_ts); 32 | } 33 | 34 | const SHA1_SIZE: usize = std::mem::size_of::(); 35 | 36 | #[wasm_bindgen] 37 | pub fn sha1_init() -> Vec { 38 | let hasher = Sha1::new(); 39 | let raw_state: [u8; SHA1_SIZE] = unsafe { std::mem::transmute(hasher) }; 40 | raw_state.to_vec() 41 | } 42 | 43 | #[wasm_bindgen] 44 | pub fn sha1_update(raw_state: &mut [u8], input: &[u8]) -> Vec { 45 | let raw_state2 = array_mut_ref!(raw_state, 0, SHA1_SIZE); 46 | let mut hasher: Sha1 = unsafe { std::mem::transmute(*raw_state2) }; 47 | hasher.input(input); 48 | let raw_state3: [u8; SHA1_SIZE] = unsafe { std::mem::transmute(hasher) }; 49 | raw_state3.to_vec() 50 | } 51 | 52 | #[wasm_bindgen] 53 | pub fn sha1_final(raw_state: &mut [u8]) -> Vec { 54 | let raw_state2 = array_mut_ref!(raw_state, 0, SHA1_SIZE); 55 | let hasher: Sha1 = unsafe { std::mem::transmute(*raw_state2) }; 56 | hasher.result().to_vec() 57 | } 58 | 59 | #[test] 60 | fn sha1_incremental_hash() { 61 | let hash_bitcoin_ts = vec![ 62 | 172, 243, 119, 55, 165, 187, 137, 56, 129, 102, 231, 172, 37, 23, 43, 80, 241, 124, 241, 63 | 186, 64 | ]; 65 | let mut state = sha1_init(); 66 | let mut state = sha1_update(state.as_mut_slice(), b"bitcoin"); 67 | let mut state = sha1_update(state.as_mut_slice(), b"-ts"); 68 | assert_eq!(sha1_final(state.as_mut_slice()), hash_bitcoin_ts); 69 | } 70 | -------------------------------------------------------------------------------- /wasm/hashes/sha256/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | pkg -------------------------------------------------------------------------------- /wasm/hashes/sha256/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha256" 3 | version = "0.1.0" 4 | authors = ["Jason Dreyzehner "] 5 | description = "Sha256 for Libauth" 6 | license = "MIT" 7 | repository = "https://github.com/bitauth/libauth/" 8 | publish = false 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | arrayref = '0.3.4' 15 | sha2 = '0.7.1' 16 | wasm-bindgen = '0.2.11' 17 | 18 | [profile.release] 19 | panic = 'abort' 20 | debug = false 21 | lto = true 22 | opt-level = 'z' 23 | -------------------------------------------------------------------------------- /wasm/hashes/sha256/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro, wasm_import_module, wasm_custom_section)] 2 | 3 | extern crate sha2; 4 | extern crate wasm_bindgen; 5 | #[macro_use] 6 | extern crate arrayref; 7 | 8 | use sha2::{Digest, Sha256}; 9 | use wasm_bindgen::prelude::*; 10 | 11 | #[wasm_bindgen] 12 | pub fn sha256(input: &[u8]) -> Vec { 13 | return Sha256::digest(input).to_vec(); 14 | } 15 | 16 | #[test] 17 | fn sha256_hash() { 18 | let hash_abc = vec![ 19 | 186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97, 163, 20 | 150, 23, 122, 156, 180, 16, 255, 97, 242, 0, 21, 173, 21 | ]; 22 | assert_eq!(sha256(b"abc"), hash_abc); 23 | let hash_test = vec![ 24 | 159, 134, 208, 129, 136, 76, 125, 101, 154, 47, 234, 160, 197, 90, 208, 21, 163, 191, 79, 25 | 27, 43, 11, 130, 44, 209, 93, 108, 21, 176, 240, 10, 8, 26 | ]; 27 | assert_eq!(sha256(b"test"), hash_test); 28 | let hash_bitcoin_ts = vec![ 29 | 197, 172, 209, 87, 32, 54, 111, 116, 79, 74, 33, 12, 216, 172, 180, 55, 181, 8, 52, 10, 69, 30 | 75, 79, 77, 6, 145, 161, 201, 161, 182, 67, 158, 31 | ]; 32 | assert_eq!(sha256(b"bitcoin-ts"), hash_bitcoin_ts); 33 | } 34 | 35 | const SHA256_SIZE: usize = std::mem::size_of::(); 36 | 37 | #[wasm_bindgen] 38 | pub fn sha256_init() -> Vec { 39 | let hasher = Sha256::new(); 40 | let raw_state: [u8; SHA256_SIZE] = unsafe { std::mem::transmute(hasher) }; 41 | raw_state.to_vec() 42 | } 43 | 44 | #[wasm_bindgen] 45 | pub fn sha256_update(raw_state: &mut [u8], input: &[u8]) -> Vec { 46 | let raw_state2 = array_mut_ref!(raw_state, 0, SHA256_SIZE); 47 | let mut hasher: Sha256 = unsafe { std::mem::transmute(*raw_state2) }; 48 | hasher.input(input); 49 | let raw_state3: [u8; SHA256_SIZE] = unsafe { std::mem::transmute(hasher) }; 50 | raw_state3.to_vec() 51 | } 52 | 53 | #[wasm_bindgen] 54 | pub fn sha256_final(raw_state: &mut [u8]) -> Vec { 55 | let raw_state2 = array_mut_ref!(raw_state, 0, SHA256_SIZE); 56 | let hasher: Sha256 = unsafe { std::mem::transmute(*raw_state2) }; 57 | hasher.result().to_vec() 58 | } 59 | 60 | #[test] 61 | fn sha256_incremental_hash() { 62 | let hash_bitcoin_ts = vec![ 63 | 197, 172, 209, 87, 32, 54, 111, 116, 79, 74, 33, 12, 216, 172, 180, 55, 181, 8, 52, 10, 69, 64 | 75, 79, 77, 6, 145, 161, 201, 161, 182, 67, 158, 65 | ]; 66 | let mut state = sha256_init(); 67 | let mut state = sha256_update(state.as_mut_slice(), b"bitcoin"); 68 | let mut state = sha256_update(state.as_mut_slice(), b"-ts"); 69 | assert_eq!(sha256_final(state.as_mut_slice()), hash_bitcoin_ts); 70 | } 71 | -------------------------------------------------------------------------------- /wasm/hashes/sha512/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | pkg -------------------------------------------------------------------------------- /wasm/hashes/sha512/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha512" 3 | version = "0.1.0" 4 | authors = ["Jason Dreyzehner "] 5 | description = "Sha512 for Libauth" 6 | license = "MIT" 7 | repository = "https://github.com/bitauth/libauth/" 8 | publish = false 9 | 10 | [lib] 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | arrayref = '0.3.4' 15 | sha2 = '0.7.1' 16 | wasm-bindgen = '0.2.11' 17 | 18 | [profile.release] 19 | panic = 'abort' 20 | debug = false 21 | lto = true 22 | opt-level = 'z' 23 | -------------------------------------------------------------------------------- /wasm/hashes/sha512/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro, wasm_import_module, wasm_custom_section)] 2 | 3 | extern crate sha2; 4 | extern crate wasm_bindgen; 5 | #[macro_use] 6 | extern crate arrayref; 7 | 8 | use sha2::{Digest, Sha512}; 9 | use wasm_bindgen::prelude::*; 10 | 11 | #[wasm_bindgen] 12 | pub fn sha512(input: &[u8]) -> Vec { 13 | return Sha512::digest(input).to_vec(); 14 | } 15 | 16 | #[test] 17 | fn sha512_hash() { 18 | let hash_abc = vec![ 19 | 221, 175, 53, 161, 147, 97, 122, 186, 204, 65, 115, 73, 174, 32, 65, 49, 18, 230, 250, 78, 20 | 137, 169, 126, 162, 10, 158, 238, 230, 75, 85, 211, 154, 33, 146, 153, 42, 39, 79, 193, 21 | 168, 54, 186, 60, 35, 163, 254, 235, 189, 69, 77, 68, 35, 100, 60, 232, 14, 42, 154, 201, 22 | 79, 165, 76, 164, 159, 23 | ]; 24 | assert_eq!(sha512(b"abc"), hash_abc); 25 | let hash_test = vec![ 26 | 238, 38, 176, 221, 74, 247, 231, 73, 170, 26, 142, 227, 193, 10, 233, 146, 63, 97, 137, 27 | 128, 119, 46, 71, 63, 136, 25, 165, 212, 148, 14, 13, 178, 122, 193, 133, 248, 160, 225, 28 | 213, 248, 79, 136, 188, 136, 127, 214, 123, 20, 55, 50, 195, 4, 204, 95, 169, 173, 142, 29 | 111, 87, 245, 0, 40, 168, 255, 30 | ]; 31 | assert_eq!(sha512(b"test"), hash_test); 32 | let hash_bitcoin_ts = vec![ 33 | 199, 3, 62, 254, 211, 112, 236, 45, 153, 174, 172, 201, 56, 4, 81, 75, 63, 108, 8, 154, 34 | 220, 157, 74, 51, 3, 125, 152, 147, 138, 57, 239, 39, 144, 71, 255, 181, 173, 73, 150, 146, 35 | 149, 26, 151, 201, 54, 28, 80, 219, 128, 183, 24, 114, 55, 231, 4, 126, 200, 17, 11, 95, 36 | 50, 70, 85, 60, 37 | ]; 38 | assert_eq!(sha512(b"bitcoin-ts"), hash_bitcoin_ts); 39 | } 40 | 41 | const SHA512_SIZE: usize = std::mem::size_of::(); 42 | 43 | #[wasm_bindgen] 44 | pub fn sha512_init() -> Vec { 45 | let hasher = Sha512::new(); 46 | let raw_state: [u8; SHA512_SIZE] = unsafe { std::mem::transmute(hasher) }; 47 | raw_state.to_vec() 48 | } 49 | 50 | #[wasm_bindgen] 51 | pub fn sha512_update(raw_state: &mut [u8], input: &[u8]) -> Vec { 52 | let raw_state2 = array_mut_ref!(raw_state, 0, SHA512_SIZE); 53 | let mut hasher: Sha512 = unsafe { std::mem::transmute(*raw_state2) }; 54 | hasher.input(input); 55 | let raw_state3: [u8; SHA512_SIZE] = unsafe { std::mem::transmute(hasher) }; 56 | raw_state3.to_vec() 57 | } 58 | 59 | #[wasm_bindgen] 60 | pub fn sha512_final(raw_state: &mut [u8]) -> Vec { 61 | let raw_state2 = array_mut_ref!(raw_state, 0, SHA512_SIZE); 62 | let hasher: Sha512 = unsafe { std::mem::transmute(*raw_state2) }; 63 | hasher.result().to_vec() 64 | } 65 | 66 | #[test] 67 | fn sha512_incremental_hash() { 68 | let hash_bitcoin_ts = vec![ 69 | 199, 3, 62, 254, 211, 112, 236, 45, 153, 174, 172, 201, 56, 4, 81, 75, 63, 108, 8, 154, 70 | 220, 157, 74, 51, 3, 125, 152, 147, 138, 57, 239, 39, 144, 71, 255, 181, 173, 73, 150, 146, 71 | 149, 26, 151, 201, 54, 28, 80, 219, 128, 183, 24, 114, 55, 231, 4, 126, 200, 17, 11, 95, 72 | 50, 70, 85, 60, 73 | ]; 74 | let mut state = sha512_init(); 75 | let mut state = sha512_update(state.as_mut_slice(), b"bitcoin"); 76 | let mut state = sha512_update(state.as_mut_slice(), b"-ts"); 77 | assert_eq!(sha512_final(state.as_mut_slice()), hash_bitcoin_ts); 78 | } 79 | --------------------------------------------------------------------------------