├── .dockerignore ├── .editorconfig ├── .eslintfiles ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── docker.yml │ └── docs.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── bench ├── .gitignore ├── account.js ├── bench.js ├── blockstore.js └── nsec.js ├── bin ├── _seeder ├── cli ├── hs-seeder ├── hsd ├── hsd-cli ├── hsd-rpc ├── hsw ├── hsw-cli ├── hsw-rpc ├── node ├── spvnode └── wallet ├── docs ├── .gitignore ├── README.md ├── install.md ├── release-files.md ├── release-notes │ ├── release-notes-4.x.md │ ├── release-notes-5.x.md │ ├── release-notes-6.x.md │ └── release-notes-7.x.md └── release-process.md ├── eslint.config.cjs ├── etc └── genesis ├── jsdoc.json ├── lib ├── blockchain │ ├── chain.js │ ├── chaindb.js │ ├── chainentry.js │ ├── common.js │ ├── index.js │ ├── layout.js │ ├── migrations.js │ └── records.js ├── blockstore │ ├── README.md │ ├── abstract.js │ ├── common.js │ ├── file.js │ ├── index-browser.js │ ├── index.js │ ├── layout.js │ ├── level.js │ └── records.js ├── client │ ├── index.js │ ├── node.js │ └── wallet.js ├── coins │ ├── coinentry.js │ ├── coins.js │ ├── coinview.js │ ├── compress.js │ ├── index.js │ └── undocoins.js ├── covenants │ ├── bitfield.js │ ├── index.js │ ├── locked-browser.js │ ├── locked.js │ ├── lockup.db │ ├── lockup.json │ ├── namedelta.js │ ├── names.db │ ├── names.json │ ├── namestate.js │ ├── ownership.js │ ├── reserved-browser.js │ ├── reserved.js │ ├── rules.js │ ├── undo.js │ └── view.js ├── dns │ ├── common.js │ ├── key.js │ ├── nsec.js │ ├── resource.js │ └── server.js ├── errors.js ├── hd │ ├── README.md │ ├── common.js │ ├── hd.js │ ├── index.js │ ├── mnemonic.js │ ├── nfkd-compat.js │ ├── nfkd.js │ ├── private.js │ ├── public.js │ ├── udata.json │ ├── unorm.js │ ├── wordlist-browser.js │ ├── wordlist.js │ └── words │ │ ├── chinese-simplified.js │ │ ├── chinese-traditional.js │ │ ├── english.js │ │ ├── french.js │ │ ├── index.js │ │ ├── italian.js │ │ ├── japanese.js │ │ ├── portuguese.js │ │ └── spanish.js ├── hsd-browser.js ├── hsd.js ├── mempool │ ├── airdropentry.js │ ├── claimentry.js │ ├── contractstate.js │ ├── fees.js │ ├── index.js │ ├── layout.js │ ├── mempool.js │ └── mempoolentry.js ├── migrations │ ├── README.md │ ├── migration.js │ ├── migrator.js │ └── state.js ├── mining │ ├── common.js │ ├── cpuminer.js │ ├── index.js │ ├── mine.js │ ├── miner.js │ └── template.js ├── net │ ├── bip152.js │ ├── brontide.js │ ├── common.js │ ├── framer.js │ ├── hostlist.js │ ├── index.js │ ├── lookup.js │ ├── netaddress.js │ ├── packets.js │ ├── parser.js │ ├── peer.js │ ├── pool.js │ ├── seeds │ │ ├── index.js │ │ ├── main.js │ │ └── testnet.js │ └── slidingwindow.js ├── node │ ├── fullnode.js │ ├── http.js │ ├── index.js │ ├── node.js │ ├── rpc.js │ ├── seeder.js │ └── spvnode.js ├── pkg.js ├── primitives │ ├── abstractblock.js │ ├── address.js │ ├── airdropkey.js │ ├── airdropproof.js │ ├── block.js │ ├── claim.js │ ├── coin.js │ ├── covenant.js │ ├── headers.js │ ├── index.js │ ├── input.js │ ├── invitem.js │ ├── keyring.js │ ├── memblock.js │ ├── merkleblock.js │ ├── mtx.js │ ├── outpoint.js │ ├── output.js │ ├── tx.js │ └── txmeta.js ├── protocol │ ├── consensus.js │ ├── errors.js │ ├── genesis-data.json │ ├── genesis.js │ ├── index.js │ ├── network.js │ ├── networks.js │ ├── policy.js │ └── timedata.js ├── script │ ├── common.js │ ├── index.js │ ├── opcode.js │ ├── script.js │ ├── scripterror.js │ ├── scriptnum.js │ ├── sigcache.js │ ├── stack.js │ └── witness.js ├── types.js ├── ui │ ├── amount.js │ ├── index.js │ └── uri.js ├── utils │ ├── binary.js │ ├── fixed.js │ ├── hashlist.js │ ├── index.js │ └── util.js ├── wallet │ ├── account.js │ ├── client.js │ ├── common.js │ ├── http.js │ ├── index.js │ ├── layout.js │ ├── masterkey.js │ ├── migrations.js │ ├── node.js │ ├── nodeclient.js │ ├── nullclient.js │ ├── path.js │ ├── paths.js │ ├── plugin.js │ ├── records.js │ ├── rpc.js │ ├── txdb.js │ ├── wallet.js │ ├── walletcoinview.js │ ├── walletdb.js │ └── walletkey.js └── workers │ ├── child-browser.js │ ├── child.js │ ├── framer.js │ ├── index.js │ ├── jobs.js │ ├── master.js │ ├── packets.js │ ├── parent-browser.js │ ├── parent.js │ ├── parser.js │ ├── worker.js │ └── workerpool.js ├── package-lock.json ├── package.json ├── scripts └── gen-hsclient.js └── test ├── address-test.js ├── airdrop-test.js ├── anyone-can-renew-test.js ├── auction-reorg-test.js ├── auction-rpc-test.js ├── auction-test.js ├── block-test.js ├── blockstore-test.js ├── brontide-test.js ├── chain-bip9-test.js ├── chain-blockstore-test.js ├── chain-checkpoints-test.js ├── chain-full-test.js ├── chain-icann-lockup-test.js ├── chain-locktime-test.js ├── chain-migration-test.js ├── chain-prune-test.js ├── chain-reset-reorg-test.js ├── chain-test.js ├── chain-tree-compaction-test.js ├── claim-test.js ├── coin-test.js ├── coins-test.js ├── consensus-test.js ├── contractstate-test.js ├── data ├── README.md ├── addresses.json ├── airdrop-proof.base64 ├── block14361.raw ├── block34663.raw ├── block37831.raw ├── block45288-undo.raw ├── block45288.raw ├── block55737-undo.raw ├── block55737.raw ├── block56940-undo.raw ├── block56940.raw ├── block57955-undo.raw ├── block57955.raw ├── block64498-undo.raw ├── block64498.raw ├── block65302-undo.raw ├── block65302.raw ├── coin1.json ├── coin1.raw ├── faucet-proof.base64 ├── hd.json ├── merkle27032.raw ├── merkle43269.raw ├── migrations │ ├── README.md │ ├── chain-0-migrate-migrations-gen.js │ ├── chain-0-migrate-migrations.json │ ├── chain-1-chainstate-gen.js │ ├── chain-1-chainstate.json │ ├── chain-2-blockstore-gen.js │ ├── chain-2-blockstore.json │ ├── chain-3-treestate-gen.js │ ├── chain-3-treestate.json │ ├── chain-4-migrationstate-v1.json │ ├── wallet-0-migrate-migrations-gen.js │ ├── wallet-0-migrate-migrations.json │ ├── wallet-1-change-gen.js │ ├── wallet-1-change.json │ ├── wallet-2-account-lookahead-gen.js │ ├── wallet-2-account-lookahead.json │ ├── wallet-4-bid-reveal-gen.js │ ├── wallet-4-bid-reveal.json │ ├── wallet-5-pagination-2.json │ ├── wallet-5-pagination-gen-2.js │ ├── wallet-5-pagination-gen.js │ ├── wallet-5-pagination.json │ └── wallet-6-migrationstate-v1.json ├── mnemonic-english.json ├── mnemonic-japanese.json ├── mnemonic-portuguese.json ├── mtx1.json ├── mtx2.json ├── mtx3.json ├── mtx4.json ├── mtx5.json ├── mtx6.json ├── netaddress-data.js ├── ns-icann.json ├── ns-names.json ├── ownership-cloudflare.zone ├── ownership-fr.zone ├── ownership-nl.zone ├── ownership-xn--ogbpf8fl.zone ├── script-tests.json ├── tx1-undo.raw └── tx1.raw ├── disable-goosig-test.js ├── dns-test.js ├── getwork-test.js ├── hd-test.js ├── headers-test.js ├── input-test.js ├── interactive-swap-test.js ├── invalid-reorg-test.js ├── mempool-invalidation-test.js ├── mempool-reorg-test.js ├── mempool-test.js ├── migrations-test.js ├── miner-test.js ├── mnemonic-test.js ├── mtx-test.js ├── namestate-test.js ├── net-hostlist-test.js ├── net-lookup-test.js ├── net-netaddress-test.js ├── net-spv-test.js ├── net-test.js ├── node-critical-error-test.js ├── node-http-test.js ├── node-rescan-test.js ├── node-rpc-test.js ├── node-spv-sync-test.js ├── node-spv-test.js ├── ns-test.js ├── ownership-test.js ├── p2p-test.js ├── path-test.js ├── reserved-test.js ├── resource-test.js ├── script-introspection-test.js ├── script-test.js ├── sighash-test.js ├── slidingwindow-test.js ├── tx-test.js ├── txmeta-test.js ├── txstart-test.js ├── util ├── balance.js ├── chain.js ├── common.js ├── memwallet.js ├── migrations.js ├── node-context.js ├── nodes-context.js ├── pagination.js ├── stub.js └── wallet.js ├── utils-test.js ├── wallet-accounts-auction-test.js ├── wallet-auction-test.js ├── wallet-balance-test.js ├── wallet-chainstate-test.js ├── wallet-change-test.js ├── wallet-coinselection-test.js ├── wallet-deepclean-test.js ├── wallet-events-test.js ├── wallet-http-test.js ├── wallet-importname-test.js ├── wallet-importnonce-test.js ├── wallet-migration-test.js ├── wallet-namestate-rescan-test.js ├── wallet-pagination-test.js ├── wallet-records-test.js ├── wallet-rescan-test.js ├── wallet-rpc-test.js ├── wallet-test.js └── wallet-unit-test.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintfiles: -------------------------------------------------------------------------------- 1 | bin/cli 2 | bin/hsd 3 | bin/node 4 | bin/hs-seeder 5 | bin/node 6 | bin/_seeder 7 | bin/spvnode 8 | bin/wallet 9 | bin/hsd-cli 10 | bin/hsw-cli 11 | etc/genesis 12 | lib/ 13 | test/ 14 | scripts/ 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | lib/protocol/genesis.json -diff 2 | lib/protocol/genesis-data.json -diff 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | name: Lint & Doc 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Setup 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 24.x 17 | 18 | - name: Install dependencies 19 | run: npm install --location=global bslint @hns-dev/bsdoc 20 | 21 | - name: Install bslintrc 22 | run: npm install bslintrc 23 | 24 | - name: Lint 25 | run: npm run lint 26 | 27 | - name: Docs 28 | run: npm run build-docs 29 | 30 | test-coverage: 31 | name: Test Coverage 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - name: Setup 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: 24.x 41 | 42 | - name: Install dependencies 43 | run: sudo apt-get update && sudo apt-get install -y libunbound-dev | 44 | npm install nyc coveralls 45 | 46 | - name: Test 47 | run: npm run test-ci 48 | 49 | - name: Coverage 50 | uses: coverallsapp/github-action@master 51 | with: 52 | github-token: ${{ secrets.GITHUB_TOKEN }} 53 | 54 | build-test: 55 | name: Build & Test 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | matrix: 59 | os: [ubuntu-latest] 60 | node: [14.x, 16.x, 18.x, 20.x, 22.x, 24.x] 61 | 62 | steps: 63 | - uses: actions/checkout@v4 64 | 65 | - name: Setup Node.js 66 | uses: actions/setup-node@v4 67 | with: 68 | node-version: ${{ matrix.node }} 69 | 70 | - name: Install libunbound 71 | if: contains(matrix.os, 'ubuntu') 72 | run: sudo apt-get update && sudo apt-get install -y libunbound-dev 73 | 74 | # Pythong 3.10->3.11 broke node-gyp. This upgrades node-gyp for older nodejs. 75 | # https://github.com/nodejs/node-gyp/issues/2219 76 | - name: Update npm. 77 | if: contains(matrix.node, '14.x') 78 | run: npm i -g npm@9 79 | 80 | - name: Install dependencies 81 | run: npm install 82 | 83 | - name: Test 84 | run: npm test 85 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Docker meta 19 | id: meta 20 | uses: docker/metadata-action@v4 21 | with: 22 | images: | 23 | ${{ secrets.DOCKERHUB_ACCOUNT }}/hsd 24 | ghcr.io/${{ github.repository }} 25 | tags: | 26 | type=semver,pattern={{version}} 27 | type=semver,pattern={{major}}.{{minor}} 28 | type=semver,pattern={{major}} 29 | type=raw,value=latest,enable=${{ github.ref != 'refs/heads/master' }} 30 | type=edge 31 | flavor: | 32 | latest=false 33 | 34 | - name: Set up QEMU 35 | uses: docker/setup-qemu-action@v2 36 | 37 | - name: Set up Docker Buildx 38 | uses: docker/setup-buildx-action@v2 39 | 40 | - name: Login to DockerHub 41 | uses: docker/login-action@v2 42 | with: 43 | username: ${{ secrets.DOCKERHUB_USERNAME }} 44 | password: ${{ secrets.DOCKERHUB_TOKEN }} 45 | 46 | - name: Login to GHCR 47 | uses: docker/login-action@v2 48 | with: 49 | registry: ghcr.io 50 | username: ${{ github.repository_owner }} 51 | password: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | - name: Build and push 54 | uses: docker/build-push-action@v3 55 | with: 56 | context: . 57 | push: true 58 | platforms: linux/amd64,linux/arm64 59 | tags: ${{ steps.meta.outputs.tags }} 60 | labels: ${{ steps.meta.outputs.labels }} 61 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | docs: 7 | name: Build docs 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 16.x 15 | - run: | 16 | npm install -g @hns-dev/bsdoc 17 | jsdoc -c jsdoc.json 18 | - name: Deploy 19 | uses: peaceiris/actions-gh-pages@v3 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | publish_dir: ./docs/reference 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | browser/hsd* 2 | build/ 3 | docker_data/ 4 | docs/reference/ 5 | node_modules/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babel* 2 | .bmocharc* 3 | .bpkgignore 4 | .editorconfig 5 | .eslint* 6 | .git* 7 | .mocharc* 8 | .nyc_output/ 9 | .yarnignore 10 | bench/ 11 | browser/hsd* 12 | build/ 13 | coverage/ 14 | docker_data/ 15 | docs/ 16 | eslint.config.cjs 17 | node_modules/ 18 | npm-debug.log 19 | package-lock.json 20 | test/ 21 | webpack.*.js 22 | yarn.lock 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Handshake 2 | 3 | ## Code style 4 | 5 | This repository includes a file [.eslintrc.json](.eslintrc.json) which sets 6 | linting preferences for the project. The continuous integration bot will 7 | install [bslint](https://www.npmjs.com/package/bslint) which vendors a specific 8 | version of eslint and [jsdoc](https://www.npmjs.com/package/jsdoc). Before 9 | submitting a pull request, please make sure your code is clean of linting errors 10 | and has a valid documentation. If you choose to use bslint and/or jsdoc, they can 11 | be installed globally in your development environment: 12 | 13 | ``` 14 | npm install bslint jsdoc -g 15 | cd hsd # must be in repository root 16 | npm run lint # command is defined in package.json 17 | npm run build-docs # also defined in package.json 18 | ``` 19 | 20 | ## Testing 21 | 22 | [bmocha](https://www.npmjs.com/package/bmocha) will be installed as a 23 | "developer dependency" if installed without the `--omit=dev` flag. The 24 | complete built-in testing suite can be run with the command: 25 | 26 | ``` 27 | cd hsd 28 | npm run test 29 | ``` 30 | 31 | You can specify a single test file to run with (for example): 32 | 33 | ``` 34 | cd hsd 35 | npm run test-file test/wallet-test.js 36 | ``` 37 | 38 | Before submitting a pull request, please make sure your code changes do not 39 | break any of the existing regression tests or linting rules. We currently use 40 | GitHub Workflows to run the testing suite on all new pull requests. 41 | 42 | Recent workflow actions are available: 43 | https://github.com/handshake-org/hsd/actions 44 | 45 | All code changes should be covered by new tests if applicable. We currently use 46 | Coveralls to examine test coverage, and a pull request that _decreases_ test 47 | coverage will likely not be reviewed by contributors or maintainers. 48 | 49 | Current test coverage details are available: 50 | https://coveralls.io/github/handshake-org/hsd 51 | 52 | ## Commit messages 53 | 54 | Whenever possible, commits should prefixed by the module they change. The module 55 | name is generally the folder name in the `lib/` directory in which the changes 56 | occur (subdirectories can usually be ignored). Please see recent 57 | [commits to master branch](https://github.com/handshake-org/hsd/commits/master) 58 | for examples of the preferred pattern. 59 | 60 | Additional examples: 61 | 62 | ``` 63 | test: increase timeouts 64 | pkg: update CHANGELOG 65 | wallet: expose importname in RPC 66 | ``` 67 | 68 | Additional commit details are always welcome after the short title. A good 69 | example of this is in 70 | [this commit](https://github.com/handshake-org/hsd/commit/c385fc59d488f5cd592a1d23554fe1c018bf26da). 71 | Note how the author used a very brief commit message as the title but then added 72 | a detailed description in the extended message. 73 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS base 2 | WORKDIR /opt/hsd 3 | RUN apk add --no-cache bash unbound-dev gmp-dev 4 | COPY package.json /opt/hsd 5 | 6 | # Install build dependencies and compile. 7 | FROM base AS build 8 | RUN apk add --no-cache g++ gcc make python3 9 | RUN npm install --omit=dev 10 | 11 | FROM base 12 | ENV PATH="${PATH}:/opt/hsd/bin:/opt/hsd/node_modules/.bin" 13 | COPY --from=build /opt/hsd/node_modules /opt/hsd/node_modules/ 14 | COPY bin /opt/hsd/bin/ 15 | COPY lib /opt/hsd/lib/ 16 | ENTRYPOINT ["hsd"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright (c) 2014-2015, Fedor Indutny (https://github.com/indutny) 4 | Copyright (c) 2014-2018, Christopher Jeffrey (https://github.com/chjj) 5 | Copyright (c) 2014-2018, Bcoin Contributors (https://github.com/bcoin-org) 6 | Copyright (c) 2018, Handshake Contributors (https://github.com/handshake-org) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Please see [Releases](https://github.com/handshake-org/hsd/releases). 6 | We recommend using the [most recently released version](https://github.com/handshake-org/hsd/releases/latest). 7 | 8 | ## Reporting a Vulnerability 9 | 10 | To report security issues send an email to a project maintainer below (not for support). 11 | 12 | The following keys may be used to communicate sensitive information to developers: 13 | 14 | | Name | Email | Fingerprint | Full Key | 15 | |-------------------------------------------------------------|------------------------------|------------------------------------------|------------------------------| 16 | | Christopher Jeffrey ([@chjj](https://github.com/chjj)) | chjjeffrey@gmail.com | B4B1F62DBAC084E333F3A04A8962AB9DE6666BBD | https://keybase.io/chjj | 17 | | Matthew Zipkin ([@pinheadmz](https://github.com/pinheadmz)) | pinheadmz@gmail.com | E61773CD6E01040E2F1BD78CE7E2984B6289C93A | https://keybase.io/pinheadmz | 18 | | Nodari Chkuaselidze ([@nodech](https://github.com/nodech)) | nodar.chkuaselidze@gmail.com | D2B2828BD29374D5E9BA3E52CCE677B05CC0FE23 | https://keybase.io/nodech | 19 | 20 | You can also import a key by running the following command with an individual’s fingerprint: 21 | 22 | `gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys ""` 23 | 24 | To import the full set: 25 | ``` 26 | gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys "B4B1F62DBAC084E333F3A04A8962AB9DE6666BBD" 27 | gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys "E61773CD6E01040E2F1BD78CE7E2984B6289C93A" 28 | gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys "D2B2828BD29374D5E9BA3E52CCE677B05CC0FE23" 29 | ``` 30 | -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/bench/.gitignore -------------------------------------------------------------------------------- /bench/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {tmpdir} = require('os'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const WalletDB = require('../lib/wallet/walletdb'); 7 | const bench = require('./bench'); 8 | 9 | const location = path.join(tmpdir(), 'hsd_account_bench_' + String(Date.now())); 10 | const wdb = new WalletDB({ 11 | network: 'regtest', 12 | memory: false, 13 | location 14 | }); 15 | 16 | (async () => { 17 | await fs.mkdirSync(location); 18 | await wdb.open(); 19 | const wallet = await wdb.create(); 20 | 21 | const end = bench('account'); 22 | for (let i = 0; i < 1000; i++) { 23 | await wallet.createAccount({}); 24 | } 25 | end(1000); 26 | })().catch((err) => { 27 | console.error(err.stack); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function bench(name) { 4 | const start = process.hrtime(); 5 | return function end(ops) { 6 | const elapsed = process.hrtime(start); 7 | const time = elapsed[0] + elapsed[1] / 1e9; 8 | const rate = ops / time; 9 | 10 | console.log('%s: ops=%d, time=%d, rate=%s', 11 | name, ops, time, rate.toFixed(5)); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /bench/nsec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const bench = require('./bench'); 4 | const nsec = require('../lib/dns/nsec'); 5 | const {TYPE_MAP_EMPTY} = require('../lib/dns/common'); 6 | 7 | const tld = 'dollarydoo'; 8 | const ops = 100000; 9 | 10 | const end = bench('nsec'); 11 | for (let i = 0; i < ops; i++) { 12 | const prev = nsec.prevName(tld); 13 | const next = nsec.nextName(tld); 14 | nsec.create(prev, next, TYPE_MAP_EMPTY); 15 | } 16 | end(ops); 17 | -------------------------------------------------------------------------------- /bin/_seeder: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const os = require('os'); 7 | const Config = require('bcfg'); 8 | const Seeder = require('../lib/node/seeder'); 9 | const config = new Config('hs-seeder'); 10 | 11 | config.load({ 12 | argv: true 13 | }); 14 | 15 | const network = config.str(['-n', '--network'], 'main'); 16 | 17 | const defaultPrefix = path.join( 18 | os.homedir(), 19 | '.hsd', 20 | network === 'main' ? '' : network 21 | ); 22 | const prefix = config.str('prefix', defaultPrefix); 23 | 24 | (async () => { 25 | const seeder = new Seeder({ 26 | network, 27 | prefix, 28 | filename: config.str(['-f', '--filename']), 29 | zone: config.str(['--zone']), 30 | ns: config.str(['--ns']), 31 | ip: config.str(['--ip']), 32 | host: config.str(['-h', '--host']), 33 | port: config.uint(['-p', '--port']) 34 | }); 35 | 36 | await seeder.open(); 37 | })().catch((err) => { 38 | console.error(err.stack); 39 | process.exit(1); 40 | }); 41 | -------------------------------------------------------------------------------- /bin/cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | console.error('%s%s', 6 | 'Warning: The `hsd cli` interface is deprecated.\n', 7 | 'Please use `hsd-cli` and `hsw-cli`.\n'); 8 | 9 | if (process.argv.length > 2 && process.argv[2] === 'wallet') { 10 | process.argv.splice(2, 1); // Evil hack. 11 | require('./hsw-cli'); 12 | } else { 13 | require('./hsd-cli'); 14 | } 15 | -------------------------------------------------------------------------------- /bin/hs-seeder: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const execPath = process.execPath || process.argv[0]; 6 | const execArgv = process.execArgv || []; 7 | const {argv, exit, stdout} = process; 8 | 9 | /* 10 | * Main 11 | */ 12 | 13 | function main() { 14 | if (!argv.includes('--daemon')) { 15 | require('./_seeder'); 16 | return; 17 | } 18 | 19 | const {spawn} = require('child_process'); 20 | const {resolve} = require('path'); 21 | const args = []; 22 | 23 | args.push(...execArgv); 24 | args.push(resolve(__dirname, '_seeder')); 25 | args.push(...argv.slice(2)); 26 | 27 | const ps = spawn(execPath, args, { 28 | stdio: 'ignore', 29 | detached: true 30 | }); 31 | 32 | stdout.write(ps.pid + '\n'); 33 | 34 | exit(0); 35 | } 36 | 37 | /* 38 | * Execute 39 | */ 40 | 41 | main(); 42 | -------------------------------------------------------------------------------- /bin/hsd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const execPath = process.execPath || process.argv[0]; 6 | const execArgv = process.execArgv || []; 7 | const {argv, exit, stdout} = process; 8 | 9 | /* 10 | * Main 11 | */ 12 | 13 | function main() { 14 | if (argv.length > 2) { 15 | switch (argv[2]) { 16 | case 'cli': 17 | argv.splice(2, 1); 18 | require('./cli'); 19 | return; 20 | case 'wallet': 21 | require('./cli'); 22 | return; 23 | case 'rpc': 24 | require('./cli'); 25 | return; 26 | } 27 | } 28 | 29 | if (!argv.includes('--daemon')) { 30 | if (argv.includes('--spv')) 31 | require('./spvnode'); 32 | else 33 | require('./node'); 34 | 35 | return; 36 | } 37 | 38 | const {spawn} = require('child_process'); 39 | const {resolve} = require('path'); 40 | 41 | const node = argv.includes('--spv') ? 'spvnode' : 'node'; 42 | const args = []; 43 | 44 | args.push(...execArgv); 45 | args.push(resolve(__dirname, node)); 46 | args.push(...argv.slice(2)); 47 | 48 | const ps = spawn(execPath, args, { 49 | stdio: 'ignore', 50 | detached: true 51 | }); 52 | 53 | stdout.write(ps.pid + '\n'); 54 | 55 | exit(0); 56 | } 57 | 58 | /* 59 | * Execute 60 | */ 61 | 62 | main(); 63 | -------------------------------------------------------------------------------- /bin/hsd-rpc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // insert `rpc` as first argument 4 | process.argv.splice(2, 0, 'rpc'); 5 | require('./hsd-cli'); 6 | -------------------------------------------------------------------------------- /bin/hsw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rl=0 4 | daemon=0 5 | 6 | if ! type perl > /dev/null 2>& 1; then 7 | if uname | grep -i 'darwin' > /dev/null; then 8 | echo 'hsw requires perl to start on OSX.' >& 2 9 | exit 1 10 | fi 11 | rl=1 12 | fi 13 | 14 | if test $rl -eq 1; then 15 | file=$(readlink -f "$0") 16 | else 17 | # Have to do it this way 18 | # because OSX isn't a real OS 19 | file=$(perl -MCwd -e "print Cwd::realpath('$0')") 20 | fi 21 | 22 | dir=$(dirname "$file") 23 | 24 | if test x"$1" = x'cli'; then 25 | shift 26 | exec "${dir}/cli" "wallet" "$@" 27 | exit 1 28 | fi 29 | 30 | for arg in "$@"; do 31 | case "$arg" in 32 | --daemon) 33 | daemon=1 34 | ;; 35 | esac 36 | done 37 | 38 | if test $daemon -eq 1; then 39 | # And yet again, OSX doesn't support something. 40 | if ! type setsid > /dev/null 2>& 1; then 41 | ( 42 | "${dir}/wallet" "$@" > /dev/null 2>& 1 & 43 | echo "$!" 44 | ) 45 | exit 0 46 | fi 47 | ( 48 | setsid "${dir}/wallet" "$@" > /dev/null 2>& 1 & 49 | echo "$!" 50 | ) 51 | exit 0 52 | else 53 | exec "${dir}/wallet" "$@" 54 | exit 1 55 | fi 56 | -------------------------------------------------------------------------------- /bin/hsw-rpc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // insert `rpc` as first argument 4 | process.argv.splice(2, 0, 'rpc'); 5 | require('./hsw-cli'); 6 | -------------------------------------------------------------------------------- /bin/node: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | Buffer.poolSize = 0; 6 | 7 | process.title = 'hsd'; 8 | 9 | if (process.argv.indexOf('--help') !== -1 10 | || process.argv.indexOf('-h') !== -1) { 11 | console.error('See the hsd docs at:'); 12 | console.error('https://handshake-org.github.io'); 13 | process.exit(1); 14 | } 15 | 16 | if (process.argv.indexOf('--version') !== -1 17 | || process.argv.indexOf('-v') !== -1) { 18 | const pkg = require('../package.json'); 19 | console.log(pkg.version); 20 | process.exit(0); 21 | } 22 | 23 | const FullNode = require('../lib/node/fullnode'); 24 | 25 | const node = new FullNode({ 26 | config: true, 27 | argv: true, 28 | env: true, 29 | logFile: true, 30 | logConsole: true, 31 | logLevel: 'debug', 32 | memory: false, 33 | workers: true, 34 | listen: false, 35 | network: 'main', 36 | loader: require 37 | }); 38 | 39 | // Temporary hack 40 | if (!node.config.bool('no-wallet') && !node.has('walletdb')) { 41 | const plugin = require('../lib/wallet/plugin'); 42 | node.use(plugin); 43 | } 44 | 45 | process.on('unhandledRejection', (err, promise) => { 46 | throw err; 47 | }); 48 | 49 | process.on('SIGINT', async () => { 50 | await node.close(); 51 | }); 52 | 53 | node.on('abort', async (err) => { 54 | const timeout = setTimeout(() => { 55 | console.error('Shutdown is taking a long time. Exiting.'); 56 | process.exit(3); 57 | }, 5000); 58 | 59 | timeout.unref(); 60 | 61 | try { 62 | console.error('Shutting down...'); 63 | await node.close(); 64 | clearTimeout(timeout); 65 | console.error(err.stack); 66 | process.exit(2); 67 | } catch (e) { 68 | console.error(`Error occurred during shutdown: ${e.message}`); 69 | process.exit(3); 70 | } 71 | }); 72 | 73 | (async () => { 74 | await node.ensure(); 75 | await node.open(); 76 | await node.connect(); 77 | node.startSync(); 78 | })().catch((err) => { 79 | console.error(err.stack); 80 | process.exit(1); 81 | }); 82 | -------------------------------------------------------------------------------- /bin/spvnode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'hsd'; 6 | 7 | if (process.argv.indexOf('--help') !== -1 8 | || process.argv.indexOf('-h') !== -1) { 9 | console.error('See the hsd docs at:'); 10 | console.error('https://handshake-org.github.io'); 11 | process.exit(1); 12 | throw new Error('Could not exit.'); 13 | } 14 | 15 | if (process.argv.indexOf('--version') !== -1 16 | || process.argv.indexOf('-v') !== -1) { 17 | const pkg = require('../package.json'); 18 | console.log(pkg.version); 19 | process.exit(0); 20 | throw new Error('Could not exit.'); 21 | } 22 | 23 | const blake2b = require('bcrypto/lib/blake2b'); 24 | const secp256k1 = require('bcrypto/lib/secp256k1'); 25 | 26 | if (blake2b.native !== 2) { 27 | console.error('Bindings for bcrypto were not built.'); 28 | console.error('Please build them before continuing.'); 29 | process.exit(1); 30 | return; 31 | } 32 | 33 | if (secp256k1.native !== 2) { 34 | console.error('Bindings for libsecp256k1 were not built.'); 35 | console.error('Please build them before continuing.'); 36 | process.exit(1); 37 | return; 38 | } 39 | 40 | const SPVNode = require('../lib/node/spvnode'); 41 | 42 | const node = new SPVNode({ 43 | config: true, 44 | argv: true, 45 | env: true, 46 | logFile: true, 47 | logConsole: true, 48 | logLevel: 'debug', 49 | memory: false, 50 | workers: true, 51 | listen: false, 52 | network: 'main', 53 | loader: require 54 | }); 55 | 56 | // Temporary hack 57 | if (!node.config.bool('no-wallet') && !node.has('walletdb')) { 58 | const plugin = require('../lib/wallet/plugin'); 59 | node.use(plugin); 60 | } 61 | 62 | process.on('unhandledRejection', (err, promise) => { 63 | throw err; 64 | }); 65 | 66 | process.on('SIGINT', async () => { 67 | await node.close(); 68 | }); 69 | 70 | node.on('abort', async (err) => { 71 | const timeout = setTimeout(() => { 72 | console.error('Shutdown is taking a long time. Exiting.'); 73 | process.exit(3); 74 | }, 5000); 75 | 76 | timeout.unref(); 77 | 78 | try { 79 | console.error('Shutting down...'); 80 | await node.close(); 81 | clearTimeout(timeout); 82 | console.error(err.stack); 83 | process.exit(2); 84 | } catch (e) { 85 | console.error(`Error occurred during shutdown: ${e.message}`); 86 | process.exit(3); 87 | } 88 | }); 89 | 90 | (async () => { 91 | await node.ensure(); 92 | await node.open(); 93 | await node.connect(); 94 | 95 | node.startSync(); 96 | })().catch((err) => { 97 | console.error(err.stack); 98 | process.exit(1); 99 | }); 100 | -------------------------------------------------------------------------------- /bin/wallet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'hsw'; 6 | 7 | if (process.argv.indexOf('--help') !== -1 8 | || process.argv.indexOf('-h') !== -1) { 9 | console.error('See the bcoin wiki at: https://github.com/bcoin-org/bcoin/wiki.'); 10 | process.exit(1); 11 | throw new Error('Could not exit.'); 12 | } 13 | 14 | if (process.argv.indexOf('--version') !== -1 15 | || process.argv.indexOf('-v') !== -1) { 16 | const pkg = require('../package.json'); 17 | console.log(pkg.version); 18 | process.exit(0); 19 | throw new Error('Could not exit.'); 20 | } 21 | 22 | const Node = require('../lib/wallet/node'); 23 | 24 | const node = new Node({ 25 | config: true, 26 | argv: true, 27 | env: true, 28 | logFile: true, 29 | logConsole: true, 30 | logLevel: 'debug', 31 | memory: false, 32 | workers: true, 33 | listen: true, 34 | loader: require 35 | }); 36 | 37 | process.on('unhandledRejection', (err, promise) => { 38 | throw err; 39 | }); 40 | 41 | process.on('SIGINT', async () => { 42 | await node.close(); 43 | }); 44 | 45 | (async () => { 46 | await node.ensure(); 47 | await node.open(); 48 | })().catch((err) => { 49 | console.error(err.stack); 50 | process.exit(1); 51 | }); 52 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/docs/.gitignore -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | HSD 2 | === 3 | 4 | ## Links 5 | - [Install](./install.md) 6 | - [Configuration](https://hsd-dev.org/guides/config.html) 7 | - [Release process](./release-process.md) 8 | - [Release files](./release-files.md) 9 | 10 | ## External Links 11 | - [Documentation repository](https://github.com/handshake-org/handshake-org.github.io) 12 | - [Guides](https://hsd-dev.org) 13 | - [API Doc](https://hsd-dev.org/api-docs/) 14 | - [Code generated docs](https://hsd-dev.org/hsd) 15 | - [Paper](https://hsd-dev.org/files/handshake.txt) 16 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rc = require('bslintrc'); 4 | 5 | module.exports = [ 6 | rc.configs.recommended, 7 | rc.configs.bcoin, 8 | { 9 | languageOptions: { 10 | globals: { 11 | ...rc.globals.node 12 | }, 13 | ecmaVersion: 'latest' 14 | } 15 | }, 16 | { 17 | files: [ 18 | 'bin/cli', 19 | 'bin/hsd', 20 | 'bin/node', 21 | 'bin/hs-seeder', 22 | 'bin/node', 23 | 'bin/_seeder', 24 | 'bin/spvnode', 25 | 'bin/wallet', 26 | 'bin/hsd-cli', 27 | 'bin/hsw-cli', 28 | 'etc/genesis', 29 | '**/*.js', 30 | '*.js' 31 | ], 32 | languageOptions: { 33 | sourceType: 'commonjs' 34 | } 35 | }, 36 | { 37 | files: ['test/{,**/}*.{js,cjs,mjs}'], 38 | languageOptions: { 39 | globals: { 40 | ...rc.globals.mocha, 41 | register: 'readable' 42 | } 43 | }, 44 | rules: { 45 | 'max-len': 'off', 46 | 'prefer-arrow-callback': 'off', 47 | 'no-return-assign': 'off' 48 | } 49 | } 50 | ]; 51 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": ["README.md", "lib/"], 7 | "exclude": [], 8 | "includePattern": ".+\\.js(doc)?$", 9 | "excludePattern": "(^|\\/|\\\\)_" 10 | }, 11 | "plugins": [ 12 | "plugins/markdown", 13 | "plugins/rm-imports" 14 | ], 15 | "templates": { 16 | "cleverLinks": false, 17 | "monospaceLinks": false 18 | }, 19 | "opts": { 20 | "template": "templates/default", 21 | "encoding": "utf8", 22 | "destination": "./docs/reference", 23 | "recurse": true, 24 | "private": true, 25 | "pedantic": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/blockchain/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - chain constants for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** @typedef {import('@handshake-org/bfilter').BloomFilter} BloomFilter */ 10 | /** @typedef {import('../types').LockFlags} LockFlags */ 11 | 12 | /** 13 | * @module blockchain/common 14 | */ 15 | 16 | /** 17 | * Locktime flags. 18 | * @enum {Number} 19 | */ 20 | 21 | exports.lockFlags = {}; 22 | 23 | /** 24 | * Consensus locktime flags (used for block validation). 25 | * @const {LockFlags} 26 | * @default 27 | */ 28 | 29 | exports.MANDATORY_LOCKTIME_FLAGS = 0; 30 | 31 | /** 32 | * Standard locktime flags (used for mempool validation). 33 | * @const {LockFlags} 34 | * @default 35 | */ 36 | 37 | exports.STANDARD_LOCKTIME_FLAGS = 0 38 | | exports.MANDATORY_LOCKTIME_FLAGS; 39 | 40 | /** 41 | * Threshold states for versionbits 42 | * @enum {Number} 43 | * @default 44 | */ 45 | 46 | exports.thresholdStates = { 47 | DEFINED: 0, 48 | STARTED: 1, 49 | LOCKED_IN: 2, 50 | ACTIVE: 3, 51 | FAILED: 4 52 | }; 53 | 54 | /** 55 | * Verify flags for blocks. 56 | * @enum {Number} 57 | * @default 58 | */ 59 | 60 | exports.flags = { 61 | VERIFY_NONE: 0, 62 | VERIFY_POW: 1 << 0, 63 | VERIFY_BODY: 1 << 1 64 | }; 65 | 66 | /** 67 | * Default block verify flags. 68 | * @const {Number} 69 | * @default 70 | */ 71 | 72 | exports.DEFAULT_FLAGS = 0 73 | | exports.flags.VERIFY_POW 74 | | exports.flags.VERIFY_BODY; 75 | 76 | /** 77 | * Interactive scan actions. 78 | * @enum {Number} 79 | * @default 80 | */ 81 | 82 | exports.scanActions = { 83 | NONE: 0, 84 | ABORT: 1, 85 | NEXT: 2, 86 | REPEAT_SET: 3, 87 | REPEAT_ADD: 4, 88 | REPEAT: 5 89 | }; 90 | 91 | /** 92 | * @typedef {Object} ActionAbort 93 | * @property {exports.scanActions} type - ABORT 94 | */ 95 | 96 | /** 97 | * @typedef {Object} ActionNext 98 | * @property {exports.scanActions} type - NEXT 99 | */ 100 | 101 | /** 102 | * @typedef {Object} ActionRepeat 103 | * @property {exports.ScanAction} type - REPEAT 104 | */ 105 | 106 | /** 107 | * @typedef {Object} ActionRepeatAdd 108 | * @property {exports.scanActions} type - REPEAT_ADD 109 | * @property {Buffer[]} chunks 110 | */ 111 | 112 | /** 113 | * @typedef {Object} ActionRepeatSet 114 | * @property {exports.scanActions} type - REPEAT_SET 115 | * @property {BloomFilter} filter 116 | */ 117 | 118 | /** 119 | * @typedef {ActionAbort 120 | * | ActionNext 121 | * | ActionRepeat 122 | * | ActionRepeatAdd 123 | * | ActionRepeatSet 124 | * } ScanAction 125 | */ 126 | -------------------------------------------------------------------------------- /lib/blockchain/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blockchain/index.js - blockchain for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module blockchain 11 | */ 12 | 13 | exports.ChainDB = require('./chaindb'); 14 | exports.ChainEntry = require('./chainentry'); 15 | exports.Chain = require('./chain'); 16 | exports.common = require('./common'); 17 | exports.layout = require('./layout'); 18 | -------------------------------------------------------------------------------- /lib/blockchain/layout.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * layout.js - blockchain data layout for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const bdb = require('bdb'); 10 | 11 | /* 12 | * Database Layout: 13 | * V -> db version 14 | * O -> chain options 15 | * R -> chain state (contains tip) 16 | * D -> versionbits deployments 17 | * e[hash] -> entry 18 | * h[hash] -> height 19 | * H[height] -> hash 20 | * n[hash] -> next hash 21 | * p[hash] -> tip index 22 | * b[hash] -> block (deprecated) 23 | * t[hash] -> extended tx 24 | * c[hash] -> coins 25 | * u[hash] -> undo coins (deprecated) 26 | * v[bit][hash] -> versionbits state 27 | * T[addr-hash][hash] -> dummy (tx by address) 28 | * C[addr-hash][hash][index] -> dummy (coin by address) 29 | * w[height] -> name undo 30 | * s -> tree state 31 | * f -> bit field 32 | * M -> migration state 33 | */ 34 | 35 | const layout = { 36 | V: bdb.key('V'), 37 | O: bdb.key('O'), 38 | R: bdb.key('R'), 39 | D: bdb.key('D'), 40 | e: bdb.key('e', ['hash256']), 41 | h: bdb.key('h', ['hash256']), 42 | H: bdb.key('H', ['uint32']), 43 | n: bdb.key('n', ['hash256']), 44 | p: bdb.key('p', ['hash256']), 45 | b: bdb.key('b', ['hash256']), 46 | t: bdb.key('t', ['hash256']), 47 | c: bdb.key('c', ['hash256', 'uint32']), 48 | u: bdb.key('u', ['hash256']), 49 | v: bdb.key('v', ['uint8', 'hash256']), 50 | T: bdb.key('T', ['hash', 'hash256']), 51 | C: bdb.key('C', ['hash', 'hash256', 'uint32']), 52 | w: bdb.key('w', ['uint32']), 53 | s: bdb.key('s'), 54 | f: bdb.key('f'), 55 | M: bdb.key('M') 56 | }; 57 | 58 | /* 59 | * Expose 60 | */ 61 | 62 | module.exports = layout; 63 | -------------------------------------------------------------------------------- /lib/blockstore/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - blockstore constants for hsd 3 | * Copyright (c) 2019, Braydon Fuller (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module blockstore/common 11 | */ 12 | 13 | /** 14 | * Block data types. 15 | * @enum {Number} 16 | */ 17 | 18 | exports.types = { 19 | BLOCK: 1, 20 | UNDO: 2, 21 | MERKLE: 3 22 | }; 23 | 24 | /** 25 | * File prefixes for block data types. 26 | * @enum {String} 27 | */ 28 | 29 | exports.prefixes = { 30 | 1: 'blk', 31 | 2: 'blu', 32 | 3: 'blm' 33 | }; 34 | -------------------------------------------------------------------------------- /lib/blockstore/index-browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blockstore/index.js - blockstore for hsd 3 | * Copyright (c) 2019, Braydon Fuller (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const {join} = require('path'); 10 | 11 | const AbstractBlockStore = require('./abstract'); 12 | const LevelBlockStore = require('./level'); 13 | 14 | /** 15 | * @module blockstore 16 | */ 17 | 18 | exports.create = (options) => { 19 | const location = join(options.prefix, 'blocks'); 20 | 21 | return new LevelBlockStore({ 22 | network: options.network, 23 | logger: options.logger, 24 | location: location, 25 | cacheSize: options.cacheSize, 26 | memory: options.memory 27 | }); 28 | }; 29 | 30 | exports.AbstractBlockStore = AbstractBlockStore; 31 | exports.LevelBlockStore = LevelBlockStore; 32 | -------------------------------------------------------------------------------- /lib/blockstore/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blockstore/index.js - blockstore for hsd 3 | * Copyright (c) 2019, Braydon Fuller (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const {join} = require('path'); 10 | 11 | const AbstractBlockStore = require('./abstract'); 12 | const LevelBlockStore = require('./level'); 13 | const FileBlockStore = require('./file'); 14 | 15 | /** 16 | * @module blockstore 17 | */ 18 | 19 | exports.create = (options) => { 20 | if (options.memory) { 21 | return new LevelBlockStore({ 22 | network: options.network, 23 | logger: options.logger, 24 | cacheSize: options.cacheSize, 25 | memory: options.memory 26 | }); 27 | } 28 | 29 | const location = join(options.prefix, 'blocks'); 30 | 31 | return new FileBlockStore({ 32 | network: options.network, 33 | logger: options.logger, 34 | location: location, 35 | cacheSize: options.cacheSize, 36 | maxFileLength: options.maxFileLength 37 | }); 38 | }; 39 | 40 | exports.AbstractBlockStore = AbstractBlockStore; 41 | exports.FileBlockStore = FileBlockStore; 42 | exports.LevelBlockStore = LevelBlockStore; 43 | -------------------------------------------------------------------------------- /lib/blockstore/layout.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blockstore/layout.js - file blockstore data layout for hsd 3 | * Copyright (c) 2019, Braydon Fuller (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const bdb = require('bdb'); 10 | 11 | /* 12 | * Database Layout: 13 | * V -> db version 14 | * F[type] -> last file record by type 15 | * f[type][fileno] -> file record by type and file number 16 | * b[type][hash] -> block record by type and block hash 17 | */ 18 | 19 | const layout = { 20 | V: bdb.key('V'), 21 | F: bdb.key('F', ['uint32']), 22 | f: bdb.key('f', ['uint32', 'uint32']), 23 | b: bdb.key('b', ['uint32', 'hash256']) 24 | }; 25 | 26 | /* 27 | * Expose 28 | */ 29 | 30 | module.exports = layout; 31 | -------------------------------------------------------------------------------- /lib/blockstore/records.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blockstore/records.js - blockstore records 3 | * Copyright (c) 2019, Braydon Fuller (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const bio = require('bufio'); 11 | 12 | /** @typedef {import('../types').BufioWriter} BufioWriter */ 13 | 14 | /** 15 | * @module blockstore/records 16 | */ 17 | 18 | /** 19 | * Block Record 20 | */ 21 | 22 | class BlockRecord extends bio.Struct { 23 | /** 24 | * Create a block record. 25 | * @constructor 26 | */ 27 | 28 | constructor(options = {}) { 29 | super(); 30 | 31 | this.file = options.file || 0; 32 | this.position = options.position || 0; 33 | this.length = options.length || 0; 34 | 35 | assert((this.file >>> 0) === this.file); 36 | assert((this.position >>> 0) === this.position); 37 | assert((this.length >>> 0) === this.length); 38 | } 39 | 40 | /** 41 | * Get size of the serialized block record. 42 | * @returns {Number} 43 | */ 44 | 45 | getSize() { 46 | return 12; 47 | } 48 | 49 | /** 50 | * Inject properties from buffer reader. 51 | * @param {bio.BufferReader} br 52 | */ 53 | 54 | read(br) { 55 | this.file = br.readU32(); 56 | this.position = br.readU32(); 57 | this.length = br.readU32(); 58 | 59 | return this; 60 | } 61 | 62 | /** 63 | * Serialize the block record. 64 | * Write block record to a buffer writer 65 | * @param {BufioWriter} bw 66 | * @returns {BufioWriter} 67 | */ 68 | 69 | write(bw) { 70 | bw.writeU32(this.file); 71 | bw.writeU32(this.position); 72 | bw.writeU32(this.length); 73 | 74 | return bw; 75 | } 76 | } 77 | 78 | /** 79 | * File Record 80 | */ 81 | 82 | class FileRecord extends bio.Struct { 83 | /** 84 | * Create a file record. 85 | * @constructor 86 | */ 87 | 88 | constructor(options = {}) { 89 | super(); 90 | 91 | this.blocks = options.blocks || 0; 92 | this.used = options.used || 0; 93 | this.length = options.length || 0; 94 | 95 | assert((this.blocks >>> 0) === this.blocks); 96 | assert((this.used >>> 0) === this.used); 97 | assert((this.length >>> 0) === this.length); 98 | } 99 | 100 | /** 101 | * Get serialized size of File Record. 102 | * @returns {Number} 103 | */ 104 | 105 | getSize() { 106 | return 12; 107 | } 108 | 109 | /** 110 | * Inject properties from buffer reader. 111 | * @param {bio.BufferReader} br 112 | */ 113 | 114 | read(br) { 115 | this.blocks = br.readU32(); 116 | this.used = br.readU32(); 117 | this.length = br.readU32(); 118 | 119 | return this; 120 | } 121 | 122 | /** 123 | * Write serialized file record to the buffer writer. 124 | * @param {BufioWriter} bw 125 | * @returns {BufioWriter} 126 | */ 127 | 128 | write(bw) { 129 | bw.writeU32(this.blocks); 130 | bw.writeU32(this.used); 131 | bw.writeU32(this.length); 132 | return bw; 133 | } 134 | } 135 | 136 | /* 137 | * Expose 138 | */ 139 | 140 | exports.BlockRecord = BlockRecord; 141 | exports.FileRecord = FileRecord; 142 | 143 | module.exports = exports; 144 | -------------------------------------------------------------------------------- /lib/client/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * client/index.js - http clients for hs 3 | * Copyright (c) 2017, Christopher Jeffrey (MIT License). 4 | * https://github.com/bcoin-org/bcoin 5 | */ 6 | 7 | 'use strict'; 8 | 9 | // NOTE: This is part of generated `hs-client`. 10 | 11 | /** 12 | * @module client 13 | */ 14 | 15 | exports.NodeClient = require('./node'); 16 | exports.WalletClient = require('./wallet'); 17 | -------------------------------------------------------------------------------- /lib/coins/compress.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * compress.js - coin compressor for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** @typedef {import('bufio').BufferReader} BufferReader */ 10 | /** @typedef {import('../types').BufioWriter} BufioWriter */ 11 | /** @typedef {import('../types').Amount} AmountValue */ 12 | /** @typedef {import('../primitives/output')} Output */ 13 | 14 | /** 15 | * @module coins/compress 16 | * @ignore 17 | */ 18 | 19 | const {encoding} = require('bufio'); 20 | 21 | /** 22 | * Compress an output. 23 | * @param {Output} output 24 | * @param {BufioWriter} bw 25 | */ 26 | 27 | function compressOutput(output, bw) { 28 | bw.writeVarint(output.value); 29 | output.address.write(bw); 30 | output.covenant.write(bw); 31 | return bw; 32 | } 33 | 34 | /** 35 | * Decompress a script from buffer reader. 36 | * @param {Output} output 37 | * @param {BufferReader} br 38 | */ 39 | 40 | function decompressOutput(output, br) { 41 | output.value = br.readVarint(); 42 | output.address.read(br); 43 | output.covenant.read(br); 44 | return output; 45 | } 46 | 47 | /** 48 | * Calculate output size. 49 | * @param {Output} output 50 | * @returns {Number} 51 | */ 52 | 53 | function sizeOutput(output) { 54 | let size = 0; 55 | size += encoding.sizeVarint(output.value); 56 | size += output.address.getSize(); 57 | size += output.covenant.getVarSize(); 58 | return size; 59 | } 60 | 61 | /** 62 | * Compress value using an exponent. Takes advantage of 63 | * the fact that many values are divisible by 10. 64 | * @see https://github.com/btcsuite/btcd/blob/master/blockchain/compress.go 65 | * @param {AmountValue} value 66 | * @returns {Number} 67 | */ 68 | 69 | function compressValue(value) { 70 | if (value === 0) 71 | return 0; 72 | 73 | let exp = 0; 74 | while (value % 10 === 0 && exp < 9) { 75 | value /= 10; 76 | exp += 1; 77 | } 78 | 79 | if (exp < 9) { 80 | const last = value % 10; 81 | value = (value - last) / 10; 82 | return 1 + 10 * (9 * value + last - 1) + exp; 83 | } 84 | 85 | return 10 + 10 * (value - 1); 86 | } 87 | 88 | /** 89 | * Decompress value. 90 | * @param {Number} value - Compressed value. 91 | * @returns {AmountValue} value 92 | */ 93 | 94 | function decompressValue(value) { 95 | if (value === 0) 96 | return 0; 97 | 98 | value -= 1; 99 | 100 | let exp = value % 10; 101 | 102 | value = (value - exp) / 10; 103 | 104 | let n; 105 | if (exp < 9) { 106 | const last = value % 9; 107 | value = (value - last) / 9; 108 | n = value * 10 + last + 1; 109 | } else { 110 | n = value + 1; 111 | } 112 | 113 | while (exp > 0) { 114 | n *= 10; 115 | exp -= 1; 116 | } 117 | 118 | return n; 119 | } 120 | 121 | // Make eslint happy. 122 | compressValue; 123 | decompressValue; 124 | 125 | /* 126 | * Expose 127 | */ 128 | 129 | exports.pack = compressOutput; 130 | exports.unpack = decompressOutput; 131 | exports.size = sizeOutput; 132 | -------------------------------------------------------------------------------- /lib/coins/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * coins/index.js - utxo management for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module coins 11 | */ 12 | 13 | exports.Coins = require('./coins'); 14 | exports.CoinView = require('./coinview'); 15 | exports.compress = require('./compress'); 16 | exports.UndoCoins = require('./undocoins'); 17 | -------------------------------------------------------------------------------- /lib/coins/undocoins.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * undocoins.js - undocoins object for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const bio = require('bufio'); 11 | const CoinEntry = require('../coins/coinentry'); 12 | 13 | /** @typedef {import('../types').BufioWriter} BufioWriter */ 14 | /** @typedef {import('../primitives/outpoint')} Outpoint */ 15 | /** @typedef {import('./coinview')} CoinView */ 16 | 17 | /** 18 | * Undo Coins 19 | * Coins need to be resurrected from somewhere 20 | * during a reorg. The undo coins store all 21 | * spent coins in a single record per block 22 | * (in a compressed format). 23 | * @alias module:coins.UndoCoins 24 | * @property {UndoCoin[]} items 25 | */ 26 | 27 | class UndoCoins extends bio.Struct { 28 | /** 29 | * Create undo coins. 30 | * @constructor 31 | */ 32 | 33 | constructor() { 34 | super(); 35 | /** @type {CoinEntry[]} */ 36 | this.items = []; 37 | } 38 | 39 | /** 40 | * Push coin entry onto undo coin array. 41 | * @param {CoinEntry} coin 42 | * @returns {Number} 43 | */ 44 | 45 | push(coin) { 46 | return this.items.push(coin); 47 | } 48 | 49 | /** 50 | * Calculate undo coins size. 51 | * @returns {Number} 52 | */ 53 | 54 | getSize() { 55 | let size = 0; 56 | 57 | size += 4; 58 | 59 | for (const coin of this.items) 60 | size += coin.getSize(); 61 | 62 | return size; 63 | } 64 | 65 | /** 66 | * Serialize all undo coins. 67 | * @param {BufioWriter} bw 68 | * @returns {BufioWriter} 69 | */ 70 | 71 | write(bw) { 72 | bw.writeU32(this.items.length); 73 | 74 | for (const coin of this.items) 75 | coin.write(bw); 76 | 77 | return bw; 78 | } 79 | 80 | /** 81 | * Inject properties from serialized data. 82 | * @param {bio.BufferReader} br 83 | * @returns {this} 84 | */ 85 | 86 | read(br) { 87 | const count = br.readU32(); 88 | 89 | for (let i = 0; i < count; i++) 90 | this.items.push(CoinEntry.read(br)); 91 | 92 | return this; 93 | } 94 | 95 | /** 96 | * Test whether the undo coins have any members. 97 | * @returns {Boolean} 98 | */ 99 | 100 | isEmpty() { 101 | return this.items.length === 0; 102 | } 103 | 104 | /** 105 | * Render the undo coins. 106 | * @returns {Buffer} 107 | */ 108 | 109 | commit() { 110 | const raw = this.encode(); 111 | this.items.length = 0; 112 | return raw; 113 | } 114 | 115 | /** 116 | * Re-apply undo coins to a view, effectively unspending them. 117 | * @param {CoinView} view 118 | * @param {Outpoint} prevout 119 | */ 120 | 121 | apply(view, prevout) { 122 | const undo = this.items.pop(); 123 | 124 | assert(undo); 125 | 126 | view.addEntry(prevout, undo); 127 | } 128 | } 129 | 130 | /* 131 | * Expose 132 | */ 133 | 134 | module.exports = UndoCoins; 135 | -------------------------------------------------------------------------------- /lib/covenants/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * covenants/index.js - covenants for hsd 3 | * Copyright (c) 2019, handshake-org developers (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module covenants 11 | */ 12 | 13 | exports.Namestate = require('./namestate'); 14 | exports.Ownership = require('./ownership'); 15 | exports.Rules = require('./rules'); 16 | -------------------------------------------------------------------------------- /lib/covenants/locked-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const sha3 = require('bcrypto/lib/sha3'); 5 | const data = require('./lockup.json'); 6 | 7 | /* 8 | * Constants 9 | */ 10 | 11 | const ZERO_HASH = sha3.zero.toString('hex'); 12 | 13 | /** 14 | * Locked up 15 | */ 16 | 17 | class LockedUp { 18 | constructor(data) { 19 | const meta = data[ZERO_HASH]; 20 | 21 | this.data = data; 22 | this.size = meta[0]; 23 | } 24 | 25 | has(hash) { 26 | assert(Buffer.isBuffer(hash) && hash.length === 32); 27 | 28 | const hex = hash.toString('hex'); 29 | const item = this.data[hex]; 30 | 31 | if (!item) 32 | return false; 33 | 34 | return Array.isArray(item); 35 | } 36 | 37 | get(hash) { 38 | assert(Buffer.isBuffer(hash) && hash.length === 32); 39 | 40 | const hex = hash.toString('hex'); 41 | const item = this.data[hex]; 42 | 43 | if (!item || !Array.isArray(item)) 44 | return null; 45 | 46 | const target = item[0]; 47 | const flags = item[1]; 48 | const index = target.indexOf('.'); 49 | 50 | assert(index !== -1); 51 | 52 | const root = (flags & 1) !== 0; 53 | const custom = (flags & 2) !== 0; 54 | const name = target.substring(0, index); 55 | 56 | return { 57 | name, 58 | hash, 59 | target, 60 | root, 61 | custom 62 | }; 63 | } 64 | 65 | hasByName(name) { 66 | assert(typeof name === 'string'); 67 | 68 | if (name.length === 0 || name.length > 63) 69 | return false; 70 | 71 | return this.has(hashName(name)); 72 | } 73 | 74 | getByName(name) { 75 | assert(typeof name === 'string'); 76 | 77 | if (name.length === 0 || name.length > 63) 78 | return null; 79 | 80 | return this.get(hashName(name)); 81 | } 82 | 83 | *entries() { 84 | const keys = Object.keys(this.data); 85 | 86 | for (const key of keys) { 87 | const hash = Buffer.from(key, 'hex'); 88 | 89 | yield [hash, this.get(hash)]; 90 | } 91 | } 92 | 93 | *keys() { 94 | const keys = Object.keys(this.data); 95 | 96 | for (const key of keys) 97 | yield Buffer.from(key, 'hex'); 98 | } 99 | 100 | *values() { 101 | for (const [, item] of this.entries()) 102 | yield item; 103 | } 104 | 105 | [Symbol.iterator]() { 106 | return this.entries(); 107 | } 108 | } 109 | 110 | function hashName(name) { 111 | const raw = Buffer.from(name.toLowerCase(), 'ascii'); 112 | return sha3.digest(raw); 113 | } 114 | 115 | exports.LockedUp = LockedUp; 116 | exports.locked = new LockedUp(data); 117 | -------------------------------------------------------------------------------- /lib/covenants/lockup.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/lib/covenants/lockup.db -------------------------------------------------------------------------------- /lib/covenants/names.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/lib/covenants/names.db -------------------------------------------------------------------------------- /lib/covenants/reserved-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const sha3 = require('bcrypto/lib/sha3'); 5 | const data = require('./names.json'); 6 | 7 | /* 8 | * Constants 9 | */ 10 | 11 | const ZERO_HASH = sha3.zero.toString('hex'); 12 | 13 | /** 14 | * Reserved 15 | */ 16 | 17 | class Reserved { 18 | constructor(data) { 19 | const meta = data[ZERO_HASH]; 20 | 21 | this.data = data; 22 | this.size = meta[0]; 23 | this.nameValue = meta[1]; 24 | this.rootValue = meta[2]; 25 | this.topValue = meta[3]; 26 | } 27 | 28 | has(hash) { 29 | assert(Buffer.isBuffer(hash) && hash.length === 32); 30 | 31 | const hex = hash.toString('hex'); 32 | const item = this.data[hex]; 33 | 34 | if (!item) 35 | return false; 36 | 37 | return Array.isArray(item); 38 | } 39 | 40 | get(hash) { 41 | assert(Buffer.isBuffer(hash) && hash.length === 32); 42 | 43 | const hex = hash.toString('hex'); 44 | const item = this.data[hex]; 45 | 46 | if (!item || !Array.isArray(item)) 47 | return null; 48 | 49 | const target = item[0]; 50 | const flags = item[1]; 51 | const index = target.indexOf('.'); 52 | 53 | assert(index !== -1); 54 | 55 | const root = (flags & 1) !== 0; 56 | const top100 = (flags & 2) !== 0; 57 | const custom = (flags & 4) !== 0; 58 | const zero = (flags & 8) !== 0; 59 | const name = target.substring(0, index); 60 | 61 | let value = this.nameValue; 62 | 63 | if (root) 64 | value += this.rootValue; 65 | 66 | if (top100) 67 | value += this.topValue; 68 | 69 | if (custom) 70 | value += item[2]; 71 | 72 | if (zero) 73 | value = 0; 74 | 75 | return { 76 | name, 77 | hash, 78 | target, 79 | value, 80 | root, 81 | top100, 82 | custom, 83 | zero 84 | }; 85 | } 86 | 87 | hasByName(name) { 88 | assert(typeof name === 'string'); 89 | 90 | if (name.length === 0 || name.length > 63) 91 | return false; 92 | 93 | return this.has(hashName(name)); 94 | } 95 | 96 | getByName(name) { 97 | assert(typeof name === 'string'); 98 | 99 | if (name.length === 0 || name.length > 63) 100 | return null; 101 | 102 | return this.get(hashName(name)); 103 | } 104 | 105 | *entries() { 106 | const keys = Object.keys(this.data); 107 | 108 | for (const key of keys) { 109 | const hash = Buffer.from(key, 'hex'); 110 | 111 | yield [hash, this.get(hash)]; 112 | } 113 | } 114 | 115 | *keys() { 116 | const keys = Object.keys(this.data); 117 | 118 | for (const key of keys) 119 | yield Buffer.from(key, 'hex'); 120 | } 121 | 122 | *values() { 123 | for (const [, item] of this.entries()) 124 | yield item; 125 | } 126 | 127 | [Symbol.iterator]() { 128 | return this.entries(); 129 | } 130 | } 131 | 132 | /* 133 | * Helpers 134 | */ 135 | 136 | function hashName(name) { 137 | const raw = Buffer.from(name.toLowerCase(), 'ascii'); 138 | return sha3.digest(raw); 139 | } 140 | 141 | /* 142 | * Expose 143 | */ 144 | 145 | module.exports = new Reserved(data); 146 | -------------------------------------------------------------------------------- /lib/covenants/undo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const bio = require('bufio'); 5 | const NameDelta = require('./namedelta'); 6 | 7 | class NameUndo extends bio.Struct { 8 | constructor() { 9 | super(); 10 | this.names = []; 11 | } 12 | 13 | fromView(view) { 14 | assert(view && view.names); 15 | 16 | for (const ns of view.names.values()) { 17 | if (!ns.hasDelta()) 18 | continue; 19 | 20 | this.names.push([ns.nameHash, ns.delta]); 21 | } 22 | 23 | return this; 24 | } 25 | 26 | getSize() { 27 | let size = 0; 28 | 29 | size += 4; 30 | 31 | for (const [, delta] of this.names) { 32 | size += 32; 33 | size += delta.getSize(); 34 | } 35 | 36 | return size; 37 | } 38 | 39 | write(bw) { 40 | bw.writeU32(this.names.length); 41 | 42 | for (const [nameHash, delta] of this.names) { 43 | bw.writeBytes(nameHash); 44 | delta.write(bw); 45 | } 46 | 47 | return bw; 48 | } 49 | 50 | read(br) { 51 | const count = br.readU32(); 52 | 53 | for (let i = 0; i < count; i++) { 54 | const nameHash = br.readBytes(32); 55 | const delta = NameDelta.read(br); 56 | 57 | this.names.push([nameHash, delta]); 58 | } 59 | 60 | return this; 61 | } 62 | 63 | static fromView(view) { 64 | return new this().fromView(view); 65 | } 66 | } 67 | 68 | module.exports = NameUndo; 69 | -------------------------------------------------------------------------------- /lib/covenants/view.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const {BufferMap} = require('buffer-map'); 5 | const NameState = require('./namestate'); 6 | const NameUndo = require('./undo'); 7 | 8 | /** @typedef {import('../types').Hash} Hash */ 9 | 10 | class View { 11 | constructor() { 12 | /** @type {BufferMap} */ 13 | this.names = new BufferMap(); 14 | } 15 | 16 | /** 17 | * @param {Object} db 18 | * @param {Hash} nameHash 19 | * @returns {NameState} 20 | */ 21 | 22 | getNameStateSync(db, nameHash) { 23 | assert(db && typeof db.getNameState === 'function'); 24 | assert(Buffer.isBuffer(nameHash)); 25 | 26 | const cache = this.names.get(nameHash); 27 | 28 | if (cache) 29 | return cache; 30 | 31 | /** @type {NameState?} */ 32 | const ns = db.getNameState(nameHash); 33 | 34 | if (!ns) { 35 | const ns = new NameState(); 36 | ns.nameHash = nameHash; 37 | this.names.set(nameHash, ns); 38 | return ns; 39 | } 40 | 41 | this.names.set(nameHash, ns); 42 | 43 | return ns; 44 | } 45 | 46 | /** 47 | * @param {Object} db 48 | * @param {Hash} nameHash 49 | * @returns {Promise} 50 | */ 51 | 52 | async getNameState(db, nameHash) { 53 | assert(db && typeof db.getNameState === 'function'); 54 | assert(Buffer.isBuffer(nameHash)); 55 | 56 | const cache = this.names.get(nameHash); 57 | 58 | if (cache) 59 | return cache; 60 | 61 | /** @type {NameState?} */ 62 | const ns = await db.getNameState(nameHash); 63 | 64 | if (!ns) { 65 | const ns = new NameState(); 66 | ns.nameHash = nameHash; 67 | this.names.set(nameHash, ns); 68 | return ns; 69 | } 70 | 71 | this.names.set(nameHash, ns); 72 | 73 | return ns; 74 | } 75 | 76 | toNameUndo() { 77 | return NameUndo.fromView(this); 78 | } 79 | } 80 | 81 | module.exports = View; 82 | -------------------------------------------------------------------------------- /lib/dns/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - dns constants for hsd 3 | * Copyright (c) 2021, The Handshake Developers (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module dns/common 11 | */ 12 | 13 | exports.DUMMY = Buffer.alloc(0); 14 | 15 | // About one mainnet Urkel Tree interval. 16 | // (60 seconds * 10 minutes * 36) 17 | exports.DEFAULT_TTL = 21600; 18 | 19 | // NS SOA RRSIG NSEC DNSKEY 20 | // Types available for the root "." 21 | exports.TYPE_MAP_ROOT = Buffer.from('000722000000000380', 'hex'); 22 | 23 | // RRSIG NSEC 24 | exports.TYPE_MAP_EMPTY = Buffer.from('0006000000000003', 'hex'); 25 | 26 | // NS RRSIG NSEC 27 | exports.TYPE_MAP_NS = Buffer.from('0006200000000003', 'hex'); 28 | 29 | // TXT RRSIG NSEC 30 | exports.TYPE_MAP_TXT = Buffer.from('0006000080000003', 'hex'); 31 | 32 | // A RRSIG NSEC 33 | exports.TYPE_MAP_A = Buffer.from('0006400000000003', 'hex'); 34 | 35 | // AAAA RRSIG NSEC 36 | exports.TYPE_MAP_AAAA = Buffer.from('0006000000080003', 'hex'); 37 | 38 | exports.hsTypes = { 39 | DS: 0, 40 | NS: 1, 41 | GLUE4: 2, 42 | GLUE6: 3, 43 | SYNTH4: 4, 44 | SYNTH6: 5, 45 | TXT: 6 46 | }; 47 | 48 | exports.hsTypesByVal = { 49 | [exports.hsTypes.DS]: 'DS', 50 | [exports.hsTypes.NS]: 'NS', 51 | [exports.hsTypes.GLUE4]: 'GLUE4', 52 | [exports.hsTypes.GLUE6]: 'GLUE6', 53 | [exports.hsTypes.SYNTH4]: 'SYNTH4', 54 | [exports.hsTypes.SYNTH6]: 'SYNTH6', 55 | [exports.hsTypes.TXT]: 'TXT' 56 | }; 57 | -------------------------------------------------------------------------------- /lib/dns/key.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * key.js - dnssec key for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const {dnssec, wire} = require('bns'); 10 | const {Record} = wire; 11 | 12 | // pub: 034fd714449d8cfcccfdaba52c64d63e3aca72be3f94bfeb60aeb5a42ed3d0c205 13 | exports.kskPriv = Buffer.from( 14 | '1c74c825c5b0f08cf6be846bfc93c423f03e3e1f6202fb2d96474b1520bbafad', 15 | 'hex'); 16 | 17 | // pub: 032399cfb3a72515ad609f09fd22954319d24b7c438dce00f535c7ee13010856e2 18 | exports.zskPriv = Buffer.from( 19 | '54276ff8604a3494c5c76d6651f14b289c7253ba636be4bfd7969308f48da47d', 20 | 'hex'); 21 | 22 | exports.ksk = Record.fromJSON({ 23 | name: '.', 24 | ttl: 10800, 25 | class: 'IN', 26 | type: 'DNSKEY', 27 | data: { 28 | flags: 257, 29 | protocol: 3, 30 | algorithm: 13, 31 | publicKey: '' 32 | + 'T9cURJ2M/Mz9q6UsZNY+Ospyvj+Uv+tgrrWkLtPQwgU/Xu5Yk0l02Sn5ua2x' 33 | + 'AQfEYIzRO6v5iA+BejMeEwNP4Q==' 34 | } 35 | }); 36 | 37 | exports.zsk = Record.fromJSON({ 38 | name: '.', 39 | ttl: 10800, 40 | class: 'IN', 41 | type: 'DNSKEY', 42 | data: { 43 | flags: 256, 44 | protocol: 3, 45 | algorithm: 13, 46 | publicKey: '' 47 | + 'I5nPs6clFa1gnwn9IpVDGdJLfEONzgD1NcfuEwEIVuIoHdZGgvVblsLNbRO+' 48 | + 'spW3nQYHg92svhy1HOjTiFBIsQ==' 49 | } 50 | }); 51 | 52 | // . DS 35215 13 2 53 | // 7C50EA94A63AEECB65B510D1EAC1846C973A89D4AB292287D5A4D715136B57A3 54 | exports.ds = dnssec.createDS(exports.ksk, dnssec.hashes.SHA256); 55 | 56 | exports.signKSK = function signKSK(section, type) { 57 | return dnssec.signType(section, type, exports.ksk, exports.kskPriv); 58 | }; 59 | 60 | exports.signZSK = function signZSK(section, type) { 61 | return dnssec.signType(section, type, exports.zsk, exports.zskPriv); 62 | }; 63 | -------------------------------------------------------------------------------- /lib/dns/nsec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const {wire, util} = require('bns'); 5 | const {Record, NSECRecord, types} = wire; 6 | const {DEFAULT_TTL} = require('./common'); 7 | 8 | function create(name, nextDomain, typeBitmap) { 9 | const rr = new Record(); 10 | const rd = new NSECRecord(); 11 | rr.name = util.fqdn(name); 12 | rr.type = types.NSEC; 13 | rr.ttl = DEFAULT_TTL; 14 | 15 | rd.nextDomain = util.fqdn(nextDomain); 16 | rd.typeBitmap = typeBitmap; 17 | rr.data = rd; 18 | 19 | return rr; 20 | } 21 | 22 | // Find the successor of a top level name 23 | function nextName(tld) { 24 | tld = util.trimFQDN(tld.toLowerCase()); 25 | 26 | // If the label is already 63 octets 27 | // increment last character by one 28 | if (tld.length === 63) { 29 | // Assuming no escaped octets are present 30 | let last = tld.charCodeAt(62); 31 | last = String.fromCharCode(last + 1); 32 | return tld.slice(0, -1) + last + '.'; 33 | } 34 | 35 | return tld + '\\000.'; 36 | } 37 | 38 | // Find the predecessor of a top level name 39 | function prevName(tld) { 40 | tld = util.trimFQDN(tld.toLowerCase()); 41 | assert(tld.length !== 0); 42 | 43 | // Decrement the last character by 1 44 | // assuming no escaped octets are present 45 | let last = tld.charCodeAt(tld.length - 1); 46 | last = String.fromCharCode(last - 1); 47 | tld = tld.slice(0, -1) + last; 48 | 49 | // See RFC4034 6.1 Canonical DNS Name Order 50 | // https://tools.ietf.org/html/rfc4034#section-6.1 51 | // Appending \255 prevents names that begin 52 | // with the decremented name from falling 53 | // in range i.e. if the name is `hello` a lexically 54 | // smaller name is `helln` append `\255` 55 | // to ensure that helln\255 > hellna 56 | // while keeping helln\255 < hello 57 | if (tld.length < 63) { 58 | tld += '\\255'; 59 | } 60 | 61 | return util.fqdn(tld); 62 | } 63 | 64 | exports.create = create; 65 | exports.prevName = prevName; 66 | exports.nextName = nextName; 67 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * errors.js - internal error objects for hsd 3 | * Copyright (c) 2022 The Handshake Developers (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module errors 11 | */ 12 | 13 | /** 14 | * Critical Error 15 | * An error severe enough to warrant shutting down the node. 16 | * @extends Error 17 | */ 18 | 19 | class CriticalError extends Error { 20 | /** 21 | * Create a verify error. 22 | * @constructor 23 | * @param {String} msg 24 | */ 25 | 26 | constructor(msg) { 27 | super(); 28 | 29 | this.type = 'CriticalError'; 30 | this.message = `Critical Error: ${msg}`; 31 | 32 | if (Error.captureStackTrace) 33 | Error.captureStackTrace(this, CriticalError); 34 | } 35 | } 36 | 37 | /* 38 | * Expose 39 | */ 40 | 41 | exports.CriticalError = CriticalError; 42 | -------------------------------------------------------------------------------- /lib/hd/README.md: -------------------------------------------------------------------------------- 1 | HD mnemonics and keys (BIP32, BIP39). 2 | 3 | Parts of this software were originally based on bitcore-lib: 4 | 5 | - https://github.com/bitpay/bitcore-lib/blob/master/lib/hdprivatekey.js 6 | - https://github.com/bitpay/bitcore-lib/blob/master/lib/hdpublickey.js 7 | - https://github.com/ryanxcharles/fullnode/blob/master/lib/bip32.js 8 | - https://github.com/bitpay/bitcore-mnemonic/blob/master/lib/mnemonic.js 9 | 10 | BIP32 11 | 12 | ``` 13 | Copyright (c) 2015-2016, Christopher Jeffrey (MIT License). 14 | https://github.com/bcoin-org/bcoin 15 | 16 | Copyright (c) 2013-2015 BitPay, Inc. 17 | 18 | Parts of this software are based on Bitcoin Core 19 | Copyright (c) 2009-2015 The Bitcoin Core developers 20 | 21 | Parts of this software are based on fullnode 22 | Copyright (c) 2014 Ryan X. Charles 23 | Copyright (c) 2014 reddit, Inc. 24 | 25 | Parts of this software are based on BitcoinJS 26 | Copyright (c) 2011 Stefan Thomas 27 | 28 | Parts of this software are based on BitcoinJ 29 | Copyright (c) 2011 Google Inc. 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | ``` 49 | 50 | BIP39 51 | 52 | ``` 53 | The MIT License (MIT) 54 | 55 | Copyright (c) 2014 BitPay 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy 58 | of this software and associated documentation files (the "Software"), to deal 59 | in the Software without restriction, including without limitation the rights 60 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | copies of the Software, and to permit persons to whom the Software is 62 | furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in 65 | all copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 70 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 71 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 72 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 73 | SOFTWARE. 74 | ``` 75 | -------------------------------------------------------------------------------- /lib/hd/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - common functions for hd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const LRU = require('blru'); 11 | const common = exports; 12 | 13 | /** @typedef {import('./private')} HDPrivateKey */ 14 | /** @typedef {import('./public')} HDPublicKey */ 15 | 16 | /** 17 | * Index at which hardening begins. 18 | * @const {Number} 19 | * @default 20 | */ 21 | 22 | common.HARDENED = 0x80000000; 23 | 24 | /** 25 | * Min entropy bits. 26 | * @const {Number} 27 | * @default 28 | */ 29 | 30 | common.MIN_ENTROPY = 128; 31 | 32 | /** 33 | * Max entropy bits. 34 | * @const {Number} 35 | * @default 36 | */ 37 | 38 | common.MAX_ENTROPY = 512; 39 | 40 | /** 41 | * LRU cache to avoid deriving keys twice. 42 | * @type {LRU} 43 | */ 44 | 45 | common.cache = new LRU(500); 46 | 47 | /** 48 | * Parse a derivation path and return an array of indexes. 49 | * @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki 50 | * @param {String} path 51 | * @param {Boolean} hard 52 | * @returns {Number[]} 53 | */ 54 | 55 | common.parsePath = function parsePath(path, hard) { 56 | assert(typeof path === 'string'); 57 | assert(typeof hard === 'boolean'); 58 | assert(path.length >= 1); 59 | assert(path.length <= 3062); 60 | 61 | const parts = path.split('/'); 62 | const root = parts[0]; 63 | 64 | if (root !== 'm' 65 | && root !== 'M' 66 | && root !== 'm\'' 67 | && root !== 'M\'') { 68 | throw new Error('Invalid path root.'); 69 | } 70 | 71 | const result = []; 72 | 73 | for (let i = 1; i < parts.length; i++) { 74 | let part = parts[i]; 75 | 76 | const hardened = part[part.length - 1] === '\''; 77 | 78 | if (hardened) 79 | part = part.slice(0, -1); 80 | 81 | if (part.length > 10) 82 | throw new Error('Path index too large.'); 83 | 84 | if (!/^\d+$/.test(part)) 85 | throw new Error('Path index is non-numeric.'); 86 | 87 | let index = parseInt(part, 10); 88 | 89 | if ((index >>> 0) !== index) 90 | throw new Error('Path index out of range.'); 91 | 92 | if (hardened) { 93 | index |= common.HARDENED; 94 | index >>>= 0; 95 | } 96 | 97 | if (!hard && (index & common.HARDENED)) 98 | throw new Error('Path index cannot be hardened.'); 99 | 100 | result.push(index); 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | /** 107 | * Test whether the key is a master key. 108 | * @param {HDPrivateKey|HDPublicKey} key 109 | * @returns {Boolean} 110 | */ 111 | 112 | common.isMaster = function isMaster(key) { 113 | return key.depth === 0 114 | && key.childIndex === 0 115 | && key.parentFingerPrint === 0; 116 | }; 117 | 118 | /** 119 | * Test whether the key is (most likely) a BIP44 account key. 120 | * @param {HDPrivateKey|HDPublicKey} key 121 | * @param {Number?} [account] 122 | * @returns {Boolean} 123 | */ 124 | 125 | common.isAccount = function isAccount(key, account) { 126 | if (account != null) { 127 | const index = (common.HARDENED | account) >>> 0; 128 | if (key.childIndex !== index) 129 | return false; 130 | } 131 | return key.depth === 3 && (key.childIndex & common.HARDENED) !== 0; 132 | }; 133 | 134 | /** 135 | * A compressed pubkey of all zeroes. 136 | * @const {Buffer} 137 | * @default 138 | */ 139 | 140 | common.ZERO_KEY = Buffer.alloc(33, 0x00); 141 | -------------------------------------------------------------------------------- /lib/hd/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * hd/index.js - hd keys for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = require('./hd'); 10 | -------------------------------------------------------------------------------- /lib/hd/nfkd-compat.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nfkd-compat.js - unicode normalization for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const unorm = require('./unorm'); 10 | 11 | function nfkd(str) { 12 | if (str.normalize) 13 | return str.normalize('NFKD'); 14 | 15 | return unorm.nfkd(str); 16 | } 17 | 18 | /* 19 | * Expose 20 | */ 21 | 22 | module.exports = nfkd; 23 | -------------------------------------------------------------------------------- /lib/hd/nfkd.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nfkd.js - unicode normalization for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Normalize unicode string. 11 | * @alias module:utils.nfkd 12 | * @param {String} str 13 | * @returns {String} 14 | */ 15 | 16 | function nfkd(str) { 17 | return str.normalize('NFKD'); 18 | } 19 | 20 | /* 21 | * Expose 22 | */ 23 | 24 | module.exports = nfkd; 25 | -------------------------------------------------------------------------------- /lib/hd/wordlist-browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * wordlist.js - wordlists for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const words = require('./words'); 10 | 11 | exports.get = function get(name) { 12 | switch (name) { 13 | case 'simplified chinese': 14 | return words.chinese.simplified; 15 | case 'traditional chinese': 16 | return words.chinese.traditional; 17 | case 'english': 18 | return words.english; 19 | case 'french': 20 | return words.french; 21 | case 'italian': 22 | return words.italian; 23 | case 'japanese': 24 | return words.japanese; 25 | case 'portuguese': 26 | return words.portuguese; 27 | case 'spanish': 28 | return words.spanish; 29 | default: 30 | throw new Error(`Unknown language: ${name}.`); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /lib/hd/wordlist.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * wordlist.js - wordlists for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | exports.get = function get(name) { 10 | switch (name) { 11 | case 'simplified chinese': 12 | return require('./words/chinese-simplified.js'); 13 | case 'traditional chinese': 14 | return require('./words/chinese-traditional.js'); 15 | case 'english': 16 | return require('./words/english.js'); 17 | case 'french': 18 | return require('./words/french.js'); 19 | case 'italian': 20 | return require('./words/italian.js'); 21 | case 'japanese': 22 | return require('./words/japanese.js'); 23 | case 'portuguese': 24 | return require('./words/portuguese.js'); 25 | case 'spanish': 26 | return require('./words/spanish.js'); 27 | default: 28 | throw new Error(`Unknown language: ${name}.`); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lib/hd/words/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * index.js - wordlists for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | exports.chinese = { 10 | simplified: require('./chinese-simplified.js'), 11 | traditional: require('./chinese-traditional.js') 12 | }; 13 | 14 | exports.english = require('./english.js'); 15 | exports.french = require('./french.js'); 16 | exports.italian = require('./italian.js'); 17 | exports.japanese = require('./japanese.js'); 18 | exports.portuguese = require('./portuguese.js'); 19 | exports.spanish = require('./spanish.js'); 20 | -------------------------------------------------------------------------------- /lib/mempool/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * mempool/index.js - mempool for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module mempool 11 | */ 12 | 13 | exports.Fees = require('./fees'); 14 | exports.layout = require('./layout'); 15 | exports.MempoolEntry = require('./mempoolentry'); 16 | exports.Mempool = require('./mempool'); 17 | -------------------------------------------------------------------------------- /lib/mempool/layout.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * layout.js - mempool data layout for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const bdb = require('bdb'); 10 | 11 | /* 12 | * Database Layout: 13 | * V -> db version 14 | * v -> serialization version 15 | * R -> tip hash 16 | * e[hash] -> entry 17 | */ 18 | 19 | const layout = { 20 | V: bdb.key('V'), 21 | v: bdb.key('v'), 22 | R: bdb.key('R'), 23 | F: bdb.key('F'), 24 | e: bdb.key('e', ['hash256']) 25 | }; 26 | 27 | /* 28 | * Expose 29 | */ 30 | 31 | module.exports = layout; 32 | -------------------------------------------------------------------------------- /lib/migrations/migration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * migrations/migration.js - abstract migration for hsd. 3 | * Copyright (c) 2021, Nodari Chkuaselidze (MIT License) 4 | */ 5 | 6 | 'use strict'; 7 | 8 | /** @typedef {import('bdb').DB} DB */ 9 | /** @typedef {ReturnType} Batch */ 10 | /** @typedef {import('./migrator').types} MigrationType */ 11 | /** @typedef {import('./migrator').MigrationContext} MigrationContext */ 12 | 13 | /** 14 | * Abstract class for single migration. 15 | * @alias module:migrations.AbstractMigration 16 | */ 17 | 18 | class AbstractMigration { 19 | /** 20 | * Create migration object. 21 | * @constructor 22 | * @param {Object} options 23 | */ 24 | 25 | constructor(options) { 26 | this.logger = options.logger.context('migration'); 27 | } 28 | 29 | /** 30 | * Check if the migration applies to the database 31 | * @returns {Promise} 32 | */ 33 | 34 | async check() { 35 | throw new Error('Abstract method.'); 36 | } 37 | 38 | /** 39 | * Run the actual migration 40 | * @param {Batch} b 41 | * @param {MigrationContext} ctx 42 | * @returns {Promise} 43 | */ 44 | 45 | async migrate(b, ctx) { 46 | throw new Error('Abstract method.'); 47 | } 48 | 49 | /** 50 | * Log warnings for skipped migrations. 51 | */ 52 | 53 | warning() { 54 | this.logger.warning('no warnings available.'); 55 | } 56 | 57 | /** 58 | * Return information about the migraiton 59 | * @returns {Object} 60 | */ 61 | 62 | static info() { 63 | return { 64 | name: 'abstract migration', 65 | description: 'abstract description' 66 | }; 67 | } 68 | } 69 | 70 | module.exports = AbstractMigration; 71 | -------------------------------------------------------------------------------- /lib/mining/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - mining utils 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const consensus = require('../protocol/consensus'); 11 | const BN = require('bcrypto/lib/bn.js'); 12 | 13 | /** 14 | * @exports mining/common 15 | */ 16 | 17 | const common = exports; 18 | 19 | /* 20 | * Constants 21 | */ 22 | 23 | const DIFF = 0x00000000ffff0000000000000000000000000000000000000000000000000000; 24 | const B192 = 0x1000000000000000000000000000000000000000000000000; 25 | const B128 = 0x100000000000000000000000000000000; 26 | const B64 = 0x10000000000000000; 27 | const B0 = 0x1; 28 | 29 | /** 30 | * Swap 32 bit endianness of uint256. 31 | * @param {Buffer} data 32 | * @returns {Buffer} 33 | */ 34 | 35 | common.swap32 = function swap32(data) { 36 | for (let i = 0; i < data.length; i += 4) { 37 | const field = data.readUInt32LE(i); 38 | data.writeUInt32BE(field, i); 39 | } 40 | 41 | return data; 42 | }; 43 | 44 | /** 45 | * Convert a uint256be to a double. 46 | * @param {Buffer} target 47 | * @returns {Number} 48 | */ 49 | 50 | common.double256 = function double256(target) { 51 | let n = 0; 52 | let hi, lo; 53 | 54 | assert(target.length === 32); 55 | 56 | hi = target.readUInt32BE(0); 57 | lo = target.readUInt32BE(4); 58 | n += (hi * 0x100000000 + lo) * B192; 59 | 60 | hi = target.readUInt32BE(8); 61 | lo = target.readUInt32BE(12); 62 | n += (hi * 0x100000000 + lo) * B128; 63 | 64 | hi = target.readUInt32BE(16); 65 | lo = target.readUInt32BE(20); 66 | n += (hi * 0x100000000 + lo) * B64; 67 | 68 | hi = target.readUInt32BE(24); 69 | lo = target.readUInt32BE(28); 70 | n += (hi * 0x100000000 + lo) * B0; 71 | 72 | return n; 73 | }; 74 | 75 | /** 76 | * Calculate mining difficulty 77 | * from little-endian target. 78 | * @param {Buffer} target 79 | * @returns {Number} 80 | */ 81 | 82 | common.getDifficulty = function getDifficulty(target) { 83 | const d = DIFF; 84 | const n = common.double256(target); 85 | 86 | if (n === 0) 87 | return d; 88 | 89 | return Math.floor(d / n); 90 | }; 91 | 92 | /** 93 | * Get target from bits as a uint256le. 94 | * @param {Number} bits 95 | * @returns {Buffer} 96 | */ 97 | 98 | common.getTarget = function getTarget(bits) { 99 | const target = consensus.fromCompact(bits); 100 | 101 | if (target.isNeg()) 102 | throw new Error('Target is negative.'); 103 | 104 | if (target.isZero()) 105 | throw new Error('Target is zero.'); 106 | 107 | if (target.bitLength() > 256) 108 | throw new Error('Target overflow.'); 109 | 110 | return target.toArrayLike(Buffer, 'be', 32); 111 | }; 112 | 113 | /** 114 | * Get bits from target. 115 | * @param {Buffer} data 116 | * @returns {Number} 117 | */ 118 | 119 | common.getBits = function getBits(data) { 120 | const target = new BN(data, 'be'); 121 | 122 | if (target.isZero()) 123 | throw new Error('Target is zero.'); 124 | 125 | if (target.bitLength() > 256) 126 | throw new Error('Target overflow.'); 127 | 128 | return consensus.toCompact(target); 129 | }; 130 | -------------------------------------------------------------------------------- /lib/mining/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * mining/index.js - mining infrastructure for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module mining 11 | */ 12 | 13 | exports.common = require('./common'); 14 | exports.CPUMiner = require('./cpuminer'); 15 | exports.mine = require('./mine'); 16 | exports.Miner = require('./miner'); 17 | exports.BlockTemplate = require('./template'); 18 | -------------------------------------------------------------------------------- /lib/mining/mine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * mine.js - mining function for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const bio = require('bufio'); 10 | const SHA3 = require('bcrypto/lib/sha3'); 11 | const BLAKE2b = require('bcrypto/lib/blake2b'); 12 | const Headers = require('../primitives/headers'); 13 | 14 | /** 15 | * Hash until the nonce overflows. 16 | * @alias module:mining.mine 17 | * @param {Buffer} raw 18 | * @param {Buffer} target 19 | * @param {Number} rounds 20 | * @returns {Buffer|null} 21 | */ 22 | 23 | function mine(raw, target, rounds) { 24 | const hdr = Headers.fromMiner(raw); 25 | const data = hdr.toPrehead(); 26 | const pad8 = hdr.padding(8); 27 | const pad32 = hdr.padding(32); 28 | 29 | let nonce = 0; 30 | 31 | // The heart and soul of the miner: match the target. 32 | for (let i = 0; i < rounds; i++) { 33 | const left = BLAKE2b.digest(data, 64); 34 | const right = SHA3.multi(data, pad8); 35 | const hash = BLAKE2b.multi(left, pad32, right); 36 | 37 | if (hash.compare(target) <= 0) 38 | return [nonce, true]; 39 | 40 | nonce += 1; 41 | 42 | bio.writeU32(data, nonce, 0); 43 | } 44 | 45 | return [nonce, false]; 46 | } 47 | 48 | /* 49 | * Expose 50 | */ 51 | 52 | module.exports = mine; 53 | -------------------------------------------------------------------------------- /lib/net/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - p2p constants for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module net/common 11 | */ 12 | 13 | const random = require('bcrypto/lib/random'); 14 | const pkg = require('../pkg'); 15 | 16 | /** 17 | * Default protocol version. 18 | * @const {Number} 19 | * @default 20 | */ 21 | 22 | exports.PROTOCOL_VERSION = 3; 23 | 24 | /** 25 | * Minimum protocol version we're willing to talk to. 26 | * @const {Number} 27 | * @default 28 | */ 29 | 30 | exports.MIN_VERSION = 1; 31 | 32 | /** 33 | * Service bits. 34 | * @enum {Number} 35 | * @default 36 | */ 37 | 38 | exports.services = { 39 | /** 40 | * Whether network services are enabled. 41 | */ 42 | 43 | NETWORK: 1 << 0, 44 | 45 | /** 46 | * Whether the peer supports BIP37. 47 | */ 48 | 49 | BLOOM: 1 << 1 50 | }; 51 | 52 | /** 53 | * Our node's services (we support everything). 54 | * @const {Number} 55 | * @default 56 | */ 57 | 58 | exports.LOCAL_SERVICES = 0 59 | | exports.services.NETWORK; 60 | 61 | /** 62 | * Required services (network). 63 | * @const {Number} 64 | * @default 65 | */ 66 | 67 | exports.REQUIRED_SERVICES = 0 68 | | exports.services.NETWORK; 69 | 70 | /** 71 | * Default user agent: `/[pkg.name]:[version]/`. 72 | * @const {String} 73 | * @default 74 | */ 75 | 76 | exports.USER_AGENT = `/${pkg.name}:${pkg.version}/`; 77 | 78 | /** 79 | * Max message size (~8mb) 80 | * @const {Number} 81 | * @default 82 | */ 83 | 84 | exports.MAX_MESSAGE = 8 * 1000 * 1000; 85 | 86 | /** 87 | * Amount of time to ban misbheaving peers. 88 | * @const {Number} 89 | * @default 90 | */ 91 | 92 | exports.BAN_TIME = 24 * 60 * 60; 93 | 94 | /** 95 | * Ban score threshold before ban is placed in effect. 96 | * @const {Number} 97 | * @default 98 | */ 99 | 100 | exports.BAN_SCORE = 100; 101 | 102 | /** 103 | * Create a nonce. 104 | * @returns {Buffer} 105 | */ 106 | 107 | exports.nonce = function nonce() { 108 | return random.randomBytes(8); 109 | }; 110 | 111 | /** 112 | * A compressed pubkey of all zeroes. 113 | * @const {Buffer} 114 | * @default 115 | */ 116 | 117 | exports.ZERO_KEY = Buffer.alloc(33, 0x00); 118 | 119 | /** 120 | * A 64 byte signature of all zeroes. 121 | * @const {Buffer} 122 | * @default 123 | */ 124 | 125 | exports.ZERO_SIG = Buffer.alloc(64, 0x00); 126 | 127 | /** 128 | * 8 zero bytes. 129 | * @const {Buffer} 130 | * @default 131 | */ 132 | 133 | exports.ZERO_NONCE = Buffer.alloc(8, 0x00); 134 | 135 | /** 136 | * Maximum inv/getdata size. 137 | * @const {Number} 138 | * @default 139 | */ 140 | 141 | exports.MAX_INV = 50000; 142 | 143 | /** 144 | * Maximum number of requests. 145 | * @const {Number} 146 | * @default 147 | */ 148 | 149 | exports.MAX_REQUEST = 5000; 150 | 151 | /** 152 | * Maximum number of block requests. 153 | * @const {Number} 154 | * @default 155 | */ 156 | 157 | exports.MAX_BLOCK_REQUEST = 50000 + 1000; 158 | 159 | /** 160 | * Maximum number of tx requests. 161 | * @const {Number} 162 | * @default 163 | */ 164 | 165 | exports.MAX_TX_REQUEST = 10000; 166 | 167 | /** 168 | * Maximum number of claim requests. 169 | * @const {Number} 170 | * @default 171 | */ 172 | 173 | exports.MAX_CLAIM_REQUEST = 1000; 174 | -------------------------------------------------------------------------------- /lib/net/framer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * framer.js - packet framer for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const Network = require('../protocol/network'); 11 | 12 | /** 13 | * Protocol Message Framer 14 | * @alias module:net.Framer 15 | */ 16 | 17 | class Framer { 18 | /** 19 | * Create a framer. 20 | * @constructor 21 | * @param {Network} network 22 | */ 23 | 24 | constructor(network) { 25 | this.network = Network.get(network); 26 | } 27 | 28 | /** 29 | * Frame a payload with a header. 30 | * @param {Number} cmd - Packet type. 31 | * @param {Buffer} payload 32 | * @returns {Buffer} Payload with header prepended. 33 | */ 34 | 35 | packet(cmd, payload) { 36 | assert((cmd & 0xff) === cmd); 37 | assert(Buffer.isBuffer(payload)); 38 | assert(payload.length <= 0xffffffff); 39 | 40 | const msg = Buffer.allocUnsafe(9 + payload.length); 41 | 42 | // Magic value 43 | msg.writeUInt32LE(this.network.magic, 0, true); 44 | 45 | // Command 46 | msg[4] = cmd; 47 | 48 | // Payload length 49 | msg.writeUInt32LE(payload.length, 5, true); 50 | 51 | payload.copy(msg, 9); 52 | 53 | return msg; 54 | } 55 | } 56 | 57 | /* 58 | * Expose 59 | */ 60 | 61 | module.exports = Framer; 62 | -------------------------------------------------------------------------------- /lib/net/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * net/index.js - p2p for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module net 11 | */ 12 | 13 | exports.bip152 = require('./bip152'); 14 | exports.common = require('./common'); 15 | exports.Framer = require('./framer'); 16 | exports.HostList = require('./hostlist'); 17 | exports.NetAddress = require('./netaddress'); 18 | exports.packets = require('./packets'); 19 | exports.Parser = require('./parser'); 20 | exports.Peer = require('./peer'); 21 | exports.Pool = require('./pool'); 22 | -------------------------------------------------------------------------------- /lib/net/lookup.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * lookup.js - dns lookup for hsd 3 | * Copyright (c) 2020, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const dns = require('bdns'); 11 | const constants = require('bns/lib/constants'); 12 | const Hints = require('bns/lib/hints'); 13 | const UnboundResolver = require('bns/lib/resolver/unbound'); 14 | const wire = require('bns/lib/wire'); 15 | 16 | /* 17 | * Resolver 18 | */ 19 | 20 | let resolver = null; 21 | 22 | /* 23 | * Lookup 24 | */ 25 | 26 | async function lookup(host, family, timeout) { 27 | if (family == null) 28 | family = null; 29 | 30 | assert(family === null || family === 4 || family === 6); 31 | 32 | const stub = new dns.Resolver(); 33 | 34 | stub.setServers([ 35 | // Cloudflare 36 | '1.1.1.1', 37 | // Google 38 | '8.8.8.8', 39 | '8.8.4.4', 40 | // OpenDNS 41 | '208.67.222.222', 42 | '208.67.220.220', 43 | '208.67.222.220', 44 | '208.67.220.222' 45 | ]); 46 | 47 | const out = []; 48 | const types = []; 49 | 50 | if (family == null || family === 4) 51 | types.push('A'); 52 | 53 | if (family == null || family === 6) 54 | types.push('AAAA'); 55 | 56 | for (const type of types) { 57 | let addrs; 58 | 59 | try { 60 | addrs = await stub.resolve(host, type, timeout); 61 | } catch (e) { 62 | continue; 63 | } 64 | 65 | out.push(...addrs); 66 | } 67 | 68 | if (out.length === 0) 69 | throw new Error('No DNS results.'); 70 | 71 | return out; 72 | } 73 | 74 | /* 75 | * Resolve 76 | */ 77 | 78 | async function resolve(name, family, timeout) { 79 | if (family == null) 80 | family = null; 81 | 82 | assert(typeof name === 'string'); 83 | assert(family === null || family === 4 || family === 6); 84 | 85 | if (!resolver) { 86 | resolver = new UnboundResolver({ 87 | tcp: false, 88 | edns: true, 89 | dnssec: false, 90 | hints: Hints.fromRoot() 91 | }); 92 | 93 | resolver.on('error', () => {}); 94 | } 95 | 96 | await resolver.open(); 97 | 98 | try { 99 | return await _resolve(name, family); 100 | } finally { 101 | await resolver.close(); 102 | } 103 | } 104 | 105 | async function _resolve(name, family) { 106 | const out = []; 107 | const types = []; 108 | 109 | if (family == null || family === 4) 110 | types.push(wire.types.A); 111 | 112 | if (family == null || family === 6) 113 | types.push(wire.types.AAAA); 114 | 115 | for (const type of types) { 116 | const res = await resolver.lookup(name, type); 117 | 118 | if (res.code !== wire.codes.NOERROR) { 119 | const typeName = constants.typeToString(type); 120 | const codeName = constants.codeToString(res.code); 121 | const err = new Error(`Query error: ${codeName} (${name} ${typeName}).`); 122 | 123 | err.name = name; 124 | err.type = type; 125 | err.code = res.code; 126 | 127 | throw err; 128 | } 129 | 130 | for (const rr of res.collect(name, type)) 131 | out.push(rr.data.address); 132 | } 133 | 134 | if (out.length === 0) 135 | throw new Error('No DNS results.'); 136 | 137 | return out; 138 | } 139 | 140 | /* 141 | * Expose 142 | */ 143 | 144 | exports.lookup = lookup; 145 | exports.resolve = resolve; 146 | -------------------------------------------------------------------------------- /lib/net/seeds/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * seeds.js - seeds for hsd 3 | * Copyright (c) 2017, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const main = require('./main'); 10 | const testnet = require('./testnet'); 11 | 12 | exports.get = function get(type) { 13 | switch (type) { 14 | case 'main': 15 | return main; 16 | case 'testnet': 17 | return testnet; 18 | default: 19 | return []; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /lib/net/seeds/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = [ 4 | // rithvikvibhu 5 | 'aksygghkgmciomeldjf5sc6rs2sgn2m34zfdz4xr7z5vguqvjis4e@129.153.177.220', 6 | // anon 7 | 'apt4rf2dfyelbivg63u47wykvdjtsl4kxzfdylkaae5s5ydldlnwu@159.69.46.23', 8 | // chjj 9 | 'ajdzrpoxsusaw4ixq4ttibxxsuh5fkkduc5qszyboidif2z25i362@173.255.209.126', 10 | // chjj 11 | 'akimcha5bck7s344dmge6k3agtxd2txi6x4qzg3mo26spvf5bjol2@74.207.247.120', 12 | // chjj 13 | 'aoihqqagbhzz6wxg43itefqvmgda4uwtky362p22kbimcyg5fdp54@172.104.214.189', 14 | // chjj 15 | 'ap5vuwabzwyz6akhesanada4skhetd2jsvpkwuqxzuaoovn5ez4xg@45.79.134.225', 16 | // handshake-enthusiast 17 | 'aiwykdz37okry3pb2lzdsgbxeg72uky2zckxmiapzstpqqmb2hnge@35.154.209.88', 18 | // Nathan.Woodburn/ 19 | 'ai7dgiwueiiwber6uhoeqfjdujxph6ueqpnaml36sicakngmnm3am@194.50.5.26', 20 | // Nathan.Woodburn/ 21 | 'aokj73pefmtrc7ikoxqiz4nrhgrxeqnnjpv4wxekteup33duneih2@194.50.5.27', 22 | // Nathan.Woodburn/ 23 | 'ajd6wzdp34c32rymlljybvbosnx75aty4rwmtpkxshvfrqufq6vuk@194.50.5.28', 24 | 25 | // Same nodes as above, but clearnet 26 | '129.153.177.220', 27 | '159.69.46.23', 28 | '173.255.209.126', 29 | '74.207.247.120', 30 | '172.104.214.189', 31 | '45.79.134.225', 32 | '35.154.209.88', 33 | '194.50.5.26', 34 | '194.50.5.27', 35 | '194.50.5.28', 36 | 37 | // Other nodes discovered by 38 | // seed.easyhandshake.com 39 | // htools-org/hnsnodes 40 | // hsdnethealth.nodech.dev 41 | '139.177.198.45', 42 | '139.59.211.187', 43 | '152.53.18.243', 44 | '44.229.138.206', 45 | '5.161.64.49', 46 | '74.91.115.209', 47 | '85.214.33.200' 48 | ]; 49 | -------------------------------------------------------------------------------- /lib/net/seeds/testnet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = [ 4 | 'aoihqqagbhzz6wxg43itefqvmgda4uwtky362p22kbimcyg5fdp54@172.104.214.189', 5 | 'ajdzrpoxsusaw4ixq4ttibxxsuh5fkkduc5qszyboidif2z25i362@173.255.209.126', 6 | 'ajk57wutnhfdzvqwqrgab3wwh4wxoqgnkz4avbln54pgj5jwefcts@172.104.177.177', 7 | 'am2lsmbzzxncaptqjo22jay3mztfwl33bxhkp7icfx7kmi5rvjaic@139.162.183.168' 8 | ]; 9 | -------------------------------------------------------------------------------- /lib/net/slidingwindow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const AsyncEmitter = require('bevent'); 5 | 6 | class SlidingWindow extends AsyncEmitter { 7 | /** 8 | * Create a sliding window counter 9 | * e.g: 10 | * ``` 11 | * new SlidingWindow({ 12 | * window: 1000, 13 | * limit: 100 14 | * }) 15 | * ``` 16 | * creates a sliding window which allows 100 requests per second 17 | * @property {Number} window - window period in milliseconds 18 | * @property {Number} limit - max requests allowed 19 | * @property {Timeout} timeout - sliding window timeout 20 | * @property {Number} current - current window counter 21 | * @property {Number} previous - previous window counter 22 | * @property {Number} timestamp - current window start time in milliseconds 23 | */ 24 | 25 | constructor(options) { 26 | super(); 27 | 28 | this.window = options.window || 1000; 29 | this.limit = options.limit || 100; 30 | 31 | this.timeout = null; 32 | this.current = 0; 33 | this.previous = 0; 34 | this.timestamp = 0; 35 | } 36 | 37 | start() { 38 | this.timestamp = Date.now(); 39 | this.timeout = setInterval(() => this.reset(), this.window); 40 | } 41 | 42 | stop() { 43 | this.timestamp = 0; 44 | clearInterval(this.timeout); 45 | } 46 | 47 | async reset() { 48 | this.previous = this.current; 49 | this.current = 0; 50 | this.timestamp = Date.now(); 51 | this.emit('reset'); 52 | } 53 | 54 | score() { 55 | const ms = Date.now() - this.timestamp; 56 | let weight = 1 - (ms / this.window); 57 | 58 | if (weight < 0) 59 | weight = 0; 60 | 61 | return this.previous * weight + this.current; 62 | } 63 | 64 | increase(count) { 65 | assert((count >>> 0) === count); 66 | this.current += count; 67 | } 68 | 69 | allow() { 70 | return this.score() < this.limit; 71 | } 72 | } 73 | 74 | module.exports = SlidingWindow; 75 | -------------------------------------------------------------------------------- /lib/node/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node/index.js - node for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module node 11 | */ 12 | 13 | exports.FullNode = require('./fullnode'); 14 | exports.HTTP = require('./http'); 15 | exports.Node = require('./node'); 16 | exports.RPC = require('./rpc'); 17 | exports.SPVNode = require('./spvnode'); 18 | -------------------------------------------------------------------------------- /lib/pkg.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pkg.js - package constants 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const pkg = exports; 10 | 11 | /** 12 | * Package Name 13 | * @const {String} 14 | * @default 15 | */ 16 | 17 | pkg.name = 'hsd'; 18 | 19 | /** 20 | * Project Name 21 | * @const {String} 22 | * @default 23 | */ 24 | 25 | pkg.core = 'hsd'; 26 | 27 | /** 28 | * Organization Name 29 | * @const {String} 30 | * @default 31 | */ 32 | 33 | pkg.organization = 'handshake-org'; 34 | 35 | /** 36 | * Currency Name 37 | * @const {String} 38 | * @default 39 | */ 40 | 41 | pkg.currency = 'handshake'; 42 | 43 | /** 44 | * Currency Unit 45 | * @const {String} 46 | * @default 47 | */ 48 | 49 | pkg.unit = 'hns'; 50 | 51 | /** 52 | * Base Unit (dollarydoos!) 53 | * @const {String} 54 | * @default 55 | */ 56 | 57 | pkg.base = 'doo'; 58 | 59 | /** 60 | * Config file name. 61 | * @const {String} 62 | * @default 63 | */ 64 | 65 | pkg.cfg = `${pkg.core}.conf`; 66 | 67 | /** 68 | * Current version string. 69 | * @const {String} 70 | * @default 71 | */ 72 | 73 | pkg.version = require('../package.json').version; 74 | 75 | /** 76 | * Repository URL. 77 | * @const {String} 78 | * @default 79 | */ 80 | 81 | pkg.url = `https://github.com/${pkg.organization}/${pkg.name}`; 82 | -------------------------------------------------------------------------------- /lib/primitives/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * primitives/index.js - handshake primitives for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module primitives 11 | */ 12 | 13 | exports.AbstractBlock = require('./abstractblock'); 14 | exports.Address = require('./address'); 15 | exports.Block = require('./block'); 16 | exports.Claim = require('./claim'); 17 | exports.Coin = require('./coin'); 18 | exports.Covenant = require('./covenant'); 19 | exports.Headers = require('./headers'); 20 | exports.Input = require('./input'); 21 | exports.InvItem = require('./invitem'); 22 | exports.KeyRing = require('./keyring'); 23 | exports.MemBlock = require('./memblock'); 24 | exports.MerkleBlock = require('./merkleblock'); 25 | exports.MTX = require('./mtx'); 26 | exports.Outpoint = require('./outpoint'); 27 | exports.Output = require('./output'); 28 | exports.TX = require('./tx'); 29 | exports.TXMeta = require('./txmeta'); 30 | -------------------------------------------------------------------------------- /lib/primitives/invitem.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * invitem.js - inv item object for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const bio = require('bufio'); 10 | 11 | /** @typedef {import('../types').Hash} Hash */ 12 | /** @typedef {import('../types').BufioWriter} BufioWriter */ 13 | 14 | /** 15 | * Inv Item 16 | * @alias module:primitives.InvItem 17 | * @constructor 18 | * @property {InvType} type 19 | * @property {Hash} hash 20 | */ 21 | 22 | class InvItem extends bio.Struct { 23 | /** 24 | * Create an inv item. 25 | * @constructor 26 | * @param {InvItem.types} type 27 | * @param {Hash} hash 28 | */ 29 | 30 | constructor(type, hash) { 31 | super(); 32 | this.type = type; 33 | this.hash = hash; 34 | } 35 | 36 | /** 37 | * Write inv item to buffer writer. 38 | * @returns {Number} 39 | */ 40 | 41 | getSize() { 42 | return 36; 43 | } 44 | 45 | /** 46 | * Write inv item to buffer writer. 47 | * @param {BufioWriter} bw 48 | * @returns {BufioWriter} 49 | */ 50 | 51 | write(bw) { 52 | bw.writeU32(this.type); 53 | bw.writeHash(this.hash); 54 | return bw; 55 | } 56 | 57 | /** 58 | * Inject properties from buffer reader. 59 | * @param {bio.BufferReader} br 60 | * @returns {this} 61 | */ 62 | 63 | read(br) { 64 | this.type = br.readU32(); 65 | this.hash = br.readHash(); 66 | return this; 67 | } 68 | 69 | /** 70 | * Test whether the inv item is a block. 71 | * @returns {Boolean} 72 | */ 73 | 74 | isBlock() { 75 | switch (this.type) { 76 | case InvItem.types.BLOCK: 77 | case InvItem.types.FILTERED_BLOCK: 78 | case InvItem.types.CMPCT_BLOCK: 79 | return true; 80 | default: 81 | return false; 82 | } 83 | } 84 | 85 | /** 86 | * Test whether the inv item is a tx. 87 | * @returns {Boolean} 88 | */ 89 | 90 | isTX() { 91 | switch (this.type) { 92 | case InvItem.types.TX: 93 | return true; 94 | default: 95 | return false; 96 | } 97 | } 98 | 99 | /** 100 | * Test whether the inv item is a claim. 101 | * @returns {Boolean} 102 | */ 103 | 104 | isClaim() { 105 | switch (this.type) { 106 | case InvItem.types.CLAIM: 107 | return true; 108 | default: 109 | return false; 110 | } 111 | } 112 | 113 | /** 114 | * Test whether the inv item is an airdrop proof. 115 | * @returns {Boolean} 116 | */ 117 | 118 | isAirdrop() { 119 | switch (this.type) { 120 | case InvItem.types.AIRDROP: 121 | return true; 122 | default: 123 | return false; 124 | } 125 | } 126 | } 127 | 128 | /** 129 | * Inv types. 130 | * @enum {Number} 131 | * @default 132 | */ 133 | 134 | InvItem.types = { 135 | TX: 1, 136 | BLOCK: 2, 137 | FILTERED_BLOCK: 3, 138 | CMPCT_BLOCK: 4, 139 | CLAIM: 5, 140 | AIRDROP: 6 141 | }; 142 | 143 | /** 144 | * Inv types by value. 145 | * @const {Object} 146 | */ 147 | 148 | InvItem.typesByVal = { 149 | 1: 'TX', 150 | 2: 'BLOCK', 151 | 3: 'FILTERED_BLOCK', 152 | 4: 'CMPCT_BLOCK', 153 | 5: 'CLAIM', 154 | 6: 'AIRDROP' 155 | }; 156 | 157 | /* 158 | * Expose 159 | */ 160 | 161 | module.exports = InvItem; 162 | -------------------------------------------------------------------------------- /lib/protocol/errors.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /*! 3 | * errors.js - error objects for hsd 4 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 5 | * https://github.com/handshake-org/hsd 6 | */ 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * @module protocol/errors 12 | */ 13 | 14 | const assert = require('bsert'); 15 | 16 | /** @typedef {import('../primitives/block')} Block */ 17 | /** @typedef {import('../primitives/tx')} TX */ 18 | /** @typedef {import('../primitives/claim')} Claim */ 19 | /** @typedef {import('../types').Hash} Hash */ 20 | 21 | /** 22 | * Verify Error 23 | * An error thrown during verification. Can be either 24 | * a mempool transaction validation error or a blockchain 25 | * block verification error. Ultimately used to send 26 | * `reject` packets to peers. 27 | * @extends Error 28 | * @property {Block|TX|Claim} msg 29 | * @property {String} code - Reject packet code. 30 | * @property {String} reason - Reject packet reason. 31 | * @property {Number} score - Ban score increase 32 | * (can be -1 for no reject packet). 33 | * @property {Boolean} malleated 34 | */ 35 | 36 | class VerifyError extends Error { 37 | /** 38 | * Create a verify error. 39 | * @constructor 40 | * @param {Block|TX|Claim} msg 41 | * @param {String} code - Reject packet code. 42 | * @param {String} reason - Reject packet reason. 43 | * @param {Number} score - Ban score increase 44 | * (can be -1 for no reject packet). 45 | * @param {Boolean} [malleated=false] 46 | */ 47 | 48 | constructor(msg, code, reason, score, malleated) { 49 | super(); 50 | 51 | assert(typeof code === 'string'); 52 | assert(typeof reason === 'string'); 53 | assert(score >= 0); 54 | 55 | this.type = 'VerifyError'; 56 | this.message = ''; 57 | this.code = code; 58 | this.reason = reason; 59 | this.score = score; 60 | this.hash = msg.hash(); 61 | this.malleated = malleated || false; 62 | 63 | this.message = `Verification failure: ${reason}` 64 | + ` (code=${code} score=${score} hash=${msg.hash().toString('hex')})`; 65 | 66 | if (Error.captureStackTrace) 67 | Error.captureStackTrace(this, VerifyError); 68 | } 69 | } 70 | 71 | /* 72 | * Expose 73 | */ 74 | 75 | exports.VerifyError = VerifyError; 76 | -------------------------------------------------------------------------------- /lib/protocol/genesis-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "AAAAAHZBOF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGixguUOSBpOPjXgjeCq9uLIRpXQx6cm2pjZdjUKJM1GOTJdW/vKtEDdfNg4FYPzHWH61Ij3fjNfH4G5goRQLFQAAAAD//wAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AdBMV3cAAAAAABTwI3ri6Phg99eRJPxRPwEuWqqNIwAAAAAAAAQgULiTf8Xe8I+fPL2n5fCMcG7bgKuliAwAAAAAAAAAAAAgLV3lhgnUlw+1SPha0HqH20DgVONMyByVHKmVpY9nTbcgENdI7aG5xnuU0yROAhFndhiptLMp6JatkEMfn0gDS60g4sApmh5GZ3NRZlXwmmSx4WsleVMN5sSlnOVlTepFGA8=", 3 | "testnet": "AAAAAHdBOF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGixguUOSBpOPjXgjeCq9uLIRpXQx6cm2pjZdjUKJM1GOTJdW/vKtEDdfNg4FYPzHWH61Ij3fjNfH4G5goRQLFQAAAAD//wAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AdBMV3cAAAAAABTwI3ri6Phg99eRJPxRPwEuWqqNIwAAAAAAAAQgULiTf8Xe8I+fPL2n5fCMcG7bgKuliAwAAAAAAAAAAAAgLV3lhgnUlw+1SPha0HqH20DgVONMyByVHKmVpY9nTbcgENdI7aG5xnuU0yROAhFndhiptLMp6JatkEMfn0gDS60g4sApmh5GZ3NRZlXwmmSx4WsleVMN5sSlnOVlTepFGA8=", 4 | "regtest": "AAAAAHhBOF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGixguUOSBpOPjXgjeCq9uLIRpXQx6cm2pjZdjUKJM1GOTJdW/vKtEDdfNg4FYPzHWH61Ij3fjNfH4G5goRQLFQAAAAD//38gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AdBMV3cAAAAAABTwI3ri6Phg99eRJPxRPwEuWqqNIwAAAAAAAAQgULiTf8Xe8I+fPL2n5fCMcG7bgKuliAwAAAAAAAAAAAAgLV3lhgnUlw+1SPha0HqH20DgVONMyByVHKmVpY9nTbcgENdI7aG5xnuU0yROAhFndhiptLMp6JatkEMfn0gDS60g4sApmh5GZ3NRZlXwmmSx4WsleVMN5sSlnOVlTepFGA8=", 5 | "simnet": "AAAAAHlBOF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGixguUOSBpOPjXgjeCq9uLIRpXQx6cm2pjZdjUKJM1GOTJdW/vKtEDdfNg4FYPzHWH61Ij3fjNfH4G5goRQLFQAAAAD//38gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AdBMV3cAAAAAABTwI3ri6Phg99eRJPxRPwEuWqqNIwAAAAAAAAQgULiTf8Xe8I+fPL2n5fCMcG7bgKuliAwAAAAAAAAAAAAgLV3lhgnUlw+1SPha0HqH20DgVONMyByVHKmVpY9nTbcgENdI7aG5xnuU0yROAhFndhiptLMp6JatkEMfn0gDS60g4sApmh5GZ3NRZlXwmmSx4WsleVMN5sSlnOVlTepFGA8=" 6 | } 7 | -------------------------------------------------------------------------------- /lib/protocol/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * protocol/index.js - protocol constants for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module protocol 11 | */ 12 | 13 | exports.consensus = require('./consensus'); 14 | exports.errors = require('./errors'); 15 | exports.Network = require('./network'); 16 | exports.networks = require('./networks'); 17 | exports.policy = require('./policy'); 18 | exports.timedata = require('./timedata'); 19 | -------------------------------------------------------------------------------- /lib/protocol/timedata.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * timedata.js - time management for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const EventEmitter = require('events'); 10 | const util = require('../utils/util'); 11 | const binary = require('../utils/binary'); 12 | 13 | /** 14 | * Time Data 15 | * An object which handles "adjusted time". This may not 16 | * look it, but this is actually a semi-consensus-critical 17 | * piece of code. It handles version packets from peers 18 | * and calculates what to offset our system clock's time by. 19 | * @alias module:protocol.TimeData 20 | * @extends EventEmitter 21 | * @property {Array} samples 22 | * @property {Object} known 23 | * @property {Number} limit 24 | * @property {Number} offset 25 | */ 26 | 27 | class TimeData extends EventEmitter { 28 | /** 29 | * Create time data. 30 | * @constructor 31 | * @param {Number} [limit=200] 32 | */ 33 | 34 | constructor(limit) { 35 | super(); 36 | 37 | if (limit == null) 38 | limit = 200; 39 | 40 | this.samples = []; 41 | this.known = new Map(); 42 | this.limit = limit; 43 | this.offset = 0; 44 | this.checked = false; 45 | } 46 | 47 | /** 48 | * Add time data. 49 | * @param {String} id 50 | * @param {Number} time 51 | */ 52 | 53 | add(id, time) { 54 | if (this.samples.length >= this.limit) 55 | return; 56 | 57 | if (this.known.has(id)) 58 | return; 59 | 60 | const sample = time - util.now(); 61 | 62 | this.known.set(id, sample); 63 | 64 | binary.insert(this.samples, sample, compare); 65 | 66 | this.emit('sample', sample, this.samples.length); 67 | 68 | if (this.samples.length >= 5 && this.samples.length % 2 === 1) { 69 | let median = this.samples[this.samples.length >>> 1]; 70 | 71 | if (Math.abs(median) >= 70 * 60) { 72 | if (!this.checked) { 73 | let match = false; 74 | 75 | for (const offset of this.samples) { 76 | if (offset !== 0 && Math.abs(offset) < 5 * 60) { 77 | match = true; 78 | break; 79 | } 80 | } 81 | 82 | if (!match) { 83 | this.checked = true; 84 | this.emit('mismatch'); 85 | } 86 | } 87 | 88 | median = 0; 89 | } 90 | 91 | this.offset = median; 92 | this.emit('offset', this.offset); 93 | } 94 | } 95 | 96 | /** 97 | * Get the current adjusted time. 98 | * @returns {Number} Adjusted Time. 99 | */ 100 | 101 | now() { 102 | return util.now() + this.offset; 103 | } 104 | 105 | /** 106 | * Adjust a timestamp. 107 | * @param {Number} time 108 | * @returns {Number} Adjusted Time. 109 | */ 110 | 111 | adjust(time) { 112 | return time + this.offset; 113 | } 114 | 115 | /** 116 | * Unadjust a timestamp. 117 | * @param {Number} time 118 | * @returns {Number} Local Time. 119 | */ 120 | 121 | local(time) { 122 | return time - this.offset; 123 | } 124 | 125 | /** 126 | * Get the current adjusted time in milliseconds. 127 | * @returns {Number} Adjusted Time. 128 | */ 129 | 130 | ms() { 131 | return Date.now() + this.offset * 1000; 132 | } 133 | } 134 | 135 | /* 136 | * Helpers 137 | */ 138 | 139 | function compare(a, b) { 140 | return a - b; 141 | } 142 | 143 | /* 144 | * Expose 145 | */ 146 | 147 | module.exports = TimeData; 148 | -------------------------------------------------------------------------------- /lib/script/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * script/index.js - handshake scripting for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module script 11 | */ 12 | 13 | exports.common = require('./common'); 14 | exports.Opcode = require('./opcode'); 15 | exports.Script = require('./script'); 16 | exports.ScriptError = require('./scripterror'); 17 | exports.ScriptNum = require('./scriptnum'); 18 | exports.sigcache = require('./sigcache'); 19 | exports.Stack = require('./stack'); 20 | exports.Witness = require('./witness'); 21 | -------------------------------------------------------------------------------- /lib/script/scripterror.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * scripterror.js - script error for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** @typedef {import('./opcode')} Opcode */ 10 | 11 | /** 12 | * Script Error 13 | * An error thrown from the scripting system, 14 | * potentially pertaining to Script execution. 15 | * @alias module:script.ScriptError 16 | * @extends Error 17 | * @property {String} message - Error message. 18 | * @property {String} code - Original code passed in. 19 | * @property {Number} op - Opcode. 20 | * @property {Number} ip - Instruction pointer. 21 | */ 22 | 23 | class ScriptError extends Error { 24 | /** 25 | * Create an error. 26 | * @constructor 27 | * @param {String} code - Error code. 28 | * @param {Opcode|String} [op] - Opcode. 29 | * @param {Number?} [ip] - Instruction pointer. 30 | */ 31 | 32 | constructor(code, op, ip) { 33 | super(); 34 | 35 | this.type = 'ScriptError'; 36 | this.code = code; 37 | this.message = code; 38 | this.op = -1; 39 | this.ip = -1; 40 | 41 | if (typeof op === 'string') { 42 | this.message = op; 43 | } else if (op) { 44 | this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`; 45 | this.op = op.value; 46 | this.ip = ip; 47 | } 48 | 49 | if (Error.captureStackTrace) 50 | Error.captureStackTrace(this, ScriptError); 51 | } 52 | } 53 | 54 | /* 55 | * Expose 56 | */ 57 | 58 | module.exports = ScriptError; 59 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * One of {@link module:constants.inv}. 5 | * @typedef {Number|String} InvType 6 | * @global 7 | */ 8 | 9 | /** 10 | * An output script type. 11 | * @see {module:constants.scriptTypes} 12 | * May sometimes be a string if specified. 13 | * @typedef {Number|String} ScriptType 14 | * @global 15 | */ 16 | 17 | /** 18 | * A subset of {@link ScriptType}, including 19 | * pubkeyhash, scripthash, witnesspubkeyhash, 20 | * and witnessscripthash. This value 21 | * specifically refers to the address prefix. 22 | * It is a network-agnostic way of representing 23 | * prefixes. May sometimes be a string if 24 | * specified. 25 | * @typedef {Number|String} AddressType 26 | * @global 27 | */ 28 | 29 | /** 30 | * A bitfield containing locktime flags. 31 | * @typedef {Number} LockFlags 32 | * @global 33 | */ 34 | 35 | /** 36 | * A bitfield containing name flags. 37 | * @typedef {Number} NameFlags 38 | * @global 39 | */ 40 | 41 | /** 42 | * Base58 string. 43 | * @typedef {String} Base58String 44 | * @global 45 | */ 46 | 47 | /** 48 | * Bech32 string. 49 | * @typedef {String} Bech32String 50 | * @global 51 | */ 52 | 53 | /** 54 | * Serialized address. 55 | * @typedef {Base58String|Bech32String} AddressString 56 | * @global 57 | */ 58 | 59 | /** 60 | * 32 byte buffer. 61 | * @typedef {Buffer} Hash 62 | * @global 63 | */ 64 | 65 | /** 66 | * Hex-string hash. 67 | * @typedef {String} HexHash 68 | * @global 69 | */ 70 | 71 | /** 72 | * Signature hash type. One of `all`, `single`, `none`, or 73 | * one of {@link constants.hashType}. 74 | * @typedef {Number} SighashType 75 | * @global 76 | */ 77 | 78 | /** 79 | * A dollarydoo amount. This is technically a 80 | * JS double float, but it is regularly 81 | * enforced to be less than 53 bits and 82 | * less than MAX_MONEY in various 83 | * functions. 84 | * @typedef {Number} Amount 85 | * @global 86 | */ 87 | 88 | /** 89 | * Rate of dollarydoos per kB. 90 | * @typedef {Amount} Rate 91 | * @global 92 | */ 93 | 94 | /** 95 | * A big number (bn.js) 96 | * @typedef {Object} BN 97 | * @global 98 | */ 99 | 100 | /** 101 | * A bitfield containing script verify flags. 102 | * @typedef {Number} VerifyFlags 103 | * @global 104 | */ 105 | 106 | /** 107 | * One of `main`, `testnet`, `regtest`, `simnet`. 108 | * @typedef {'main'|'testnet'|'regtest'|'simnet'} NetworkType 109 | * @see {network.types} 110 | * @global 111 | */ 112 | 113 | /** 114 | * One of `doo`, `uhns`, `mhns`, `hns`, `handshake`. 115 | * @typedef {'doo'|'uhns'|'mhns'|'hns'|'handshake'} AmountUnitType 116 | * @global 117 | */ 118 | 119 | /** 120 | * Raw block data. 121 | * @typedef {Buffer} RawBlock 122 | * @global 123 | */ 124 | 125 | /** @typedef {import('bufio').StaticWriter} StaticWriter */ 126 | /** @typedef {import('bufio').BufferWriter} BufferWriter */ 127 | 128 | /** 129 | * @typedef {StaticWriter|BufferWriter} BufioWriter 130 | */ 131 | 132 | module.exports = {}; 133 | -------------------------------------------------------------------------------- /lib/ui/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ui/index.js - high-level objects for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module ui 11 | */ 12 | 13 | exports.Amount = require('./amount'); 14 | exports.URI = require('./uri'); 15 | -------------------------------------------------------------------------------- /lib/utils/binary.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * binary.js - binary search utils for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Perform a binary search on a sorted array. 11 | * @param {Array} items 12 | * @param {Object} key 13 | * @param {Function} compare 14 | * @param {Boolean} [insert=false] 15 | * @returns {Number} Index. 16 | */ 17 | 18 | exports.search = function search(items, key, compare, insert) { 19 | let start = 0; 20 | let end = items.length - 1; 21 | 22 | while (start <= end) { 23 | const pos = (start + end) >>> 1; 24 | const cmp = compare(items[pos], key); 25 | 26 | if (cmp === 0) 27 | return pos; 28 | 29 | if (cmp < 0) 30 | start = pos + 1; 31 | else 32 | end = pos - 1; 33 | } 34 | 35 | if (!insert) 36 | return -1; 37 | 38 | return start; 39 | }; 40 | 41 | /** 42 | * Perform a binary insert on a sorted array. 43 | * @param {Array} items 44 | * @param {Object} item 45 | * @param {Function} compare 46 | * @param {Boolean} [uniq=false] 47 | * @returns {Number} index 48 | */ 49 | 50 | exports.insert = function insert(items, item, compare, uniq) { 51 | const i = exports.search(items, item, compare, true); 52 | 53 | if (uniq && i < items.length) { 54 | if (compare(items[i], item) === 0) 55 | return -1; 56 | } 57 | 58 | if (i === 0) 59 | items.unshift(item); 60 | else if (i === items.length) 61 | items.push(item); 62 | else 63 | items.splice(i, 0, item); 64 | 65 | return i; 66 | }; 67 | 68 | /** 69 | * Perform a binary removal on a sorted array. 70 | * @param {Array} items 71 | * @param {Object} item 72 | * @param {Function} compare 73 | * @returns {Boolean} 74 | */ 75 | 76 | exports.remove = function remove(items, item, compare) { 77 | const i = exports.search(items, item, compare, false); 78 | 79 | if (i === -1) 80 | return false; 81 | 82 | splice(items, i); 83 | 84 | return true; 85 | }; 86 | 87 | /* 88 | * Helpers 89 | */ 90 | 91 | /** 92 | * @param {Array} list 93 | * @param {Number} i 94 | */ 95 | 96 | function splice(list, i) { 97 | if (i === 0) { 98 | list.shift(); 99 | return; 100 | } 101 | 102 | let k = i + 1; 103 | 104 | while (k < list.length) 105 | list[i++] = list[k++]; 106 | 107 | list.pop(); 108 | } 109 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * utils/index.js - utils for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module utils 11 | */ 12 | 13 | exports.binary = require('./binary'); 14 | exports.fixed = require('./fixed'); 15 | exports.HashList = require('./hashlist'); 16 | exports.util = require('./util'); 17 | -------------------------------------------------------------------------------- /lib/wallet/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * common.js - commonly required functions for wallet. 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const {BufferMap} = require('buffer-map'); 10 | 11 | /** @typedef {import('../primitives/tx')} TX */ 12 | /** @typedef {import('../primitives/txmeta')} TXMeta */ 13 | /** @typedef {import('../primitives/coin')} Coin */ 14 | 15 | /** 16 | * @exports wallet/common 17 | */ 18 | 19 | const common = exports; 20 | 21 | /** 22 | * Test whether a string is eligible 23 | * to be used as a name or ID. 24 | * @param {String} key 25 | * @returns {Boolean} 26 | */ 27 | 28 | common.isName = function isName(key) { 29 | if (typeof key !== 'string') 30 | return false; 31 | 32 | if (key.length === 0) 33 | return false; 34 | 35 | if (!/^[\-\._0-9A-Za-z]+$/.test(key)) 36 | return false; 37 | 38 | // Prevents __proto__ 39 | // from being used. 40 | switch (key[0]) { 41 | case '_': 42 | case '-': 43 | case '.': 44 | return false; 45 | } 46 | 47 | switch (key[key.length - 1]) { 48 | case '_': 49 | case '-': 50 | case '.': 51 | return false; 52 | } 53 | 54 | return key.length >= 1 && key.length <= 40; 55 | }; 56 | 57 | /** 58 | * Sort an array of transactions by time. 59 | * @param {TXMeta[]} txs 60 | * @returns {TXMeta[]} 61 | */ 62 | 63 | common.sortTX = function sortTX(txs) { 64 | return txs.sort((a, b) => { 65 | return a.mtime - b.mtime; 66 | }); 67 | }; 68 | 69 | /** 70 | * Sort an array of coins by height. 71 | * @param {Coin[]} coins 72 | * @returns {Coin[]} 73 | */ 74 | 75 | common.sortCoins = function sortCoins(coins) { 76 | return coins.sort((a, b) => { 77 | const ah = a.height === -1 ? 0x7fffffff : a.height; 78 | const bh = b.height === -1 ? 0x7fffffff : b.height; 79 | return ah - bh; 80 | }); 81 | }; 82 | 83 | /** 84 | * Sort an array of transactions in dependency order. 85 | * @param {TX[]} txs 86 | * @returns {TX[]} 87 | */ 88 | 89 | common.sortDeps = function sortDeps(txs) { 90 | const map = new BufferMap(); 91 | 92 | for (const tx of txs) { 93 | const hash = tx.hash(); 94 | map.set(hash, tx); 95 | } 96 | 97 | const depMap = new BufferMap(); 98 | const depCount = new BufferMap(); 99 | const top = []; 100 | 101 | for (const [hash, tx] of map) { 102 | depCount.set(hash, 0); 103 | 104 | let hasDeps = false; 105 | 106 | for (const input of tx.inputs) { 107 | const prev = input.prevout.hash; 108 | 109 | if (!map.has(prev)) 110 | continue; 111 | 112 | const count = depCount.get(hash); 113 | depCount.set(hash, count + 1); 114 | hasDeps = true; 115 | 116 | if (!depMap.has(prev)) 117 | depMap.set(prev, []); 118 | 119 | depMap.get(prev).push(tx); 120 | } 121 | 122 | if (hasDeps) 123 | continue; 124 | 125 | top.push(tx); 126 | } 127 | 128 | const result = []; 129 | 130 | for (const tx of top) { 131 | const hash = tx.hash(); 132 | const deps = depMap.get(hash); 133 | 134 | result.push(tx); 135 | 136 | if (!deps) 137 | continue; 138 | 139 | for (const tx of deps) { 140 | const hash = tx.hash(); 141 | 142 | let count = depCount.get(hash); 143 | 144 | if (--count === 0) 145 | top.push(tx); 146 | 147 | depCount.set(hash, count); 148 | } 149 | } 150 | 151 | return result; 152 | }; 153 | -------------------------------------------------------------------------------- /lib/wallet/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * wallet/index.js - wallet for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module wallet 11 | */ 12 | 13 | exports.Account = require('./account'); 14 | exports.Client = require('./client'); 15 | exports.common = require('./common'); 16 | exports.HTTP = require('./http'); 17 | exports.layout = require('./layout'); 18 | exports.MasterKey = require('./masterkey'); 19 | exports.NodeClient = require('./nodeclient'); 20 | exports.Path = require('./path'); 21 | exports.plugin = require('./plugin'); 22 | exports.records = require('./records'); 23 | exports.RPC = require('./rpc'); 24 | exports.Node = require('./node'); 25 | exports.TXDB = require('./txdb'); 26 | exports.WalletDB = require('./walletdb'); 27 | exports.Wallet = require('./wallet'); 28 | exports.WalletKey = require('./walletkey'); 29 | -------------------------------------------------------------------------------- /lib/wallet/paths.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * paths.js - paths object for hsd 3 | * Copyright (c) 2019, Boyma Fahnbulleh (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | 11 | /** @typedef {import('./path')} Path */ 12 | 13 | /** 14 | * Paths 15 | * Represents the HD paths for coins in a single transaction. 16 | * @alias module:wallet.Paths 17 | * @property {Map[]} outputs - Paths. 18 | */ 19 | 20 | class Paths { 21 | /** 22 | * Create paths 23 | * @constructor 24 | */ 25 | 26 | constructor() { 27 | this.paths = new Map(); 28 | } 29 | 30 | /** 31 | * Add a single entry to the collection. 32 | * @param {Number} index 33 | * @param {Path} path 34 | * @returns {Path} 35 | */ 36 | 37 | add(index, path) { 38 | assert((index >>> 0) === index); 39 | assert(path); 40 | this.paths.set(index, path); 41 | return path; 42 | } 43 | 44 | /** 45 | * Test whether the collection has a path. 46 | * @param {Number} index 47 | * @returns {Boolean} 48 | */ 49 | 50 | has(index) { 51 | return this.paths.has(index); 52 | } 53 | 54 | /** 55 | * Get a path. 56 | * @param {Number} index 57 | * @returns {Path|null} 58 | */ 59 | 60 | get(index) { 61 | return this.paths.get(index) || null; 62 | } 63 | 64 | /** 65 | * Remove a path and return it. 66 | * @param {Number} index 67 | * @returns {Path|null} 68 | */ 69 | 70 | remove(index) { 71 | const path = this.get(index); 72 | 73 | if (!path) 74 | return null; 75 | 76 | this.paths.delete(index); 77 | 78 | return path; 79 | } 80 | 81 | /** 82 | * Test whether there are paths. 83 | * @returns {Boolean} 84 | */ 85 | 86 | isEmpty() { 87 | return this.paths.size === 0; 88 | } 89 | } 90 | 91 | /* 92 | * Expose 93 | */ 94 | 95 | module.exports = Paths; 96 | -------------------------------------------------------------------------------- /lib/wallet/plugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * plugin.js - wallet plugin for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const EventEmitter = require('events'); 10 | const WalletDB = require('./walletdb'); 11 | const NodeClient = require('./nodeclient'); 12 | const HTTP = require('./http'); 13 | const RPC = require('./rpc'); 14 | 15 | /** 16 | * @exports wallet/plugin 17 | */ 18 | 19 | const plugin = exports; 20 | 21 | /** 22 | * Plugin 23 | * @extends EventEmitter 24 | */ 25 | 26 | class Plugin extends EventEmitter { 27 | /** 28 | * Create a plugin. 29 | * @constructor 30 | * @param {Node} node 31 | */ 32 | 33 | constructor(node) { 34 | super(); 35 | 36 | this.config = node.config.filter('wallet', { 37 | // Allow configurations to propagate from the hsd.conf 38 | // with 'wallet-' prefix. 39 | data: true 40 | }); 41 | this.config.open('hsw.conf'); 42 | 43 | this.network = node.network; 44 | this.logger = node.logger; 45 | 46 | this.client = new NodeClient(node); 47 | 48 | this.wdb = new WalletDB({ 49 | network: this.network, 50 | logger: this.logger, 51 | workers: this.workers, 52 | client: this.client, 53 | prefix: this.config.prefix, 54 | memory: this.config.bool('memory', node.memory), 55 | maxFiles: this.config.uint('max-files'), 56 | cacheSize: this.config.mb('cache-size'), 57 | wipeNoReally: this.config.bool('wipe-no-really'), 58 | spv: node.spv, 59 | walletMigrate: this.config.uint('migrate'), 60 | icannlockup: this.config.bool('icannlockup', true), 61 | migrateNoRescan: this.config.bool('migrate-no-rescan', false), 62 | preloadAll: this.config.bool('preload-all', false), 63 | maxHistoryTXs: this.config.uint('max-history-txs', 100) 64 | }); 65 | 66 | this.rpc = new RPC(this); 67 | 68 | this.http = new HTTP({ 69 | network: this.network, 70 | logger: this.logger, 71 | node: this, 72 | ssl: this.config.bool('ssl'), 73 | keyFile: this.config.path('ssl-key'), 74 | certFile: this.config.path('ssl-cert'), 75 | host: this.config.str('http-host'), 76 | port: this.config.uint('http-port'), 77 | apiKey: this.config.str('api-key', node.config.str('api-key')), 78 | walletAuth: this.config.bool('wallet-auth'), 79 | noAuth: this.config.bool('no-auth'), 80 | cors: this.config.bool('cors'), 81 | adminToken: this.config.str('admin-token') 82 | }); 83 | 84 | this.init(); 85 | } 86 | 87 | init() { 88 | this.wdb.on('error', err => this.emit('error', err)); 89 | this.http.on('error', err => this.emit('error', err)); 90 | } 91 | 92 | async open() { 93 | await this.wdb.open(); 94 | this.rpc.wallet = this.wdb.primary; 95 | await this.http.open(); 96 | await this.wdb.connect(); 97 | } 98 | 99 | async close() { 100 | await this.http.close(); 101 | this.rpc.wallet = null; 102 | await this.wdb.disconnect(); 103 | await this.wdb.close(); 104 | } 105 | } 106 | 107 | /** 108 | * Plugin name. 109 | * @const {String} 110 | */ 111 | 112 | plugin.id = 'walletdb'; 113 | 114 | /** 115 | * Plugin initialization. 116 | * @param {Node} node 117 | * @returns {WalletDB} 118 | */ 119 | 120 | plugin.init = function init(node) { 121 | return new Plugin(node); 122 | }; 123 | -------------------------------------------------------------------------------- /lib/workers/child-browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * child.js - child processes for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const EventEmitter = require('events'); 11 | 12 | /** 13 | * Child 14 | * Represents a child process. 15 | * @alias module:workers.Child 16 | * @extends EventEmitter 17 | * @ignore 18 | */ 19 | 20 | class Child extends EventEmitter { 21 | /** 22 | * Represents a child process. 23 | * @constructor 24 | * @param {String} file 25 | */ 26 | 27 | constructor(file) { 28 | super(); 29 | 30 | this.init(file); 31 | } 32 | 33 | /** 34 | * Test whether child process support is available. 35 | * @returns {Boolean} 36 | */ 37 | 38 | static hasSupport() { 39 | return typeof global.postMessage === 'function'; 40 | } 41 | 42 | /** 43 | * Initialize child process. Bind to events. 44 | * @private 45 | * @param {String} file 46 | */ 47 | 48 | init(file) { 49 | this.child = new global.Worker(file); 50 | 51 | this.child.onerror = (event) => { 52 | this.emit('error', new Error('Child error.')); 53 | this.emit('exit', 1, null); 54 | }; 55 | 56 | this.child.onmessage = (event) => { 57 | let data; 58 | if (typeof event.data === 'string') { 59 | data = Buffer.from(event.data, 'hex'); 60 | assert(data.length === event.data.length / 2); 61 | } else { 62 | assert(event.data && typeof event.data === 'object'); 63 | assert(event.data.data && typeof event.data.data.length === 'number'); 64 | data = event.data.data; 65 | data.__proto__ = Buffer.prototype; 66 | } 67 | this.emit('data', data); 68 | }; 69 | } 70 | 71 | /** 72 | * Send data to child process. 73 | * @param {Buffer} data 74 | * @returns {Boolean} 75 | */ 76 | 77 | write(data) { 78 | if (this.child.postMessage.length === 2) { 79 | data.__proto__ = Uint8Array.prototype; 80 | this.child.postMessage({ data }, [data]); 81 | } else { 82 | this.child.postMessage(data.toString('hex')); 83 | } 84 | return true; 85 | } 86 | 87 | /** 88 | * Destroy the child process. 89 | */ 90 | 91 | destroy() { 92 | this.child.terminate(); 93 | this.emit('exit', 15 | 0x80, 'SIGTERM'); 94 | } 95 | } 96 | 97 | /* 98 | * Expose 99 | */ 100 | 101 | module.exports = Child; 102 | -------------------------------------------------------------------------------- /lib/workers/framer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * workers.js - worker processes for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const bio = require('bufio'); 10 | 11 | /** 12 | * Framer 13 | * @alias module:workers.Framer 14 | */ 15 | 16 | class Framer { 17 | /** 18 | * Create a framer. 19 | * @constructor 20 | */ 21 | 22 | constructor() {} 23 | 24 | packet(payload) { 25 | const size = 10 + payload.getSize(); 26 | const bw = bio.write(size); 27 | 28 | bw.writeU32(payload.id); 29 | bw.writeU8(payload.cmd); 30 | bw.seek(4); 31 | 32 | payload.write(bw); 33 | 34 | bw.writeU8(0x0a); 35 | 36 | const msg = bw.render(); 37 | msg.writeUInt32LE(msg.length - 10, 5, true); 38 | 39 | return msg; 40 | } 41 | } 42 | 43 | /* 44 | * Expose 45 | */ 46 | 47 | module.exports = Framer; 48 | -------------------------------------------------------------------------------- /lib/workers/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * workers/index.js - workers for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @module workers 11 | */ 12 | 13 | exports.Framer = require('./framer'); 14 | exports.jobs = require('./jobs'); 15 | exports.packets = require('./packets'); 16 | exports.Parser = require('./parser'); 17 | exports.WorkerPool = require('./workerpool'); 18 | -------------------------------------------------------------------------------- /lib/workers/parent-browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parent.js - worker processes for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('bsert'); 10 | const EventEmitter = require('events'); 11 | 12 | /** 13 | * Parent 14 | * Represents the parent process. 15 | * @alias module:workers.Parent 16 | * @extends EventEmitter 17 | * @ignore 18 | */ 19 | 20 | class Parent extends EventEmitter { 21 | /** 22 | * Create the parent process. 23 | * @constructor 24 | */ 25 | 26 | constructor() { 27 | super(); 28 | 29 | this.init(); 30 | } 31 | 32 | /** 33 | * Initialize master (web workers). 34 | * @private 35 | */ 36 | 37 | init() { 38 | global.onerror = (event) => { 39 | this.emit('error', new Error('Worker error.')); 40 | }; 41 | 42 | global.onmessage = (event) => { 43 | let data; 44 | if (typeof event.data === 'string') { 45 | data = Buffer.from(event.data, 'hex'); 46 | assert(data.length === event.data.length / 2); 47 | } else { 48 | assert(event.data && typeof event.data === 'object'); 49 | assert(event.data.data && typeof event.data.data.length === 'number'); 50 | data = event.data.data; 51 | data.__proto__ = Buffer.prototype; 52 | } 53 | this.emit('data', data); 54 | }; 55 | } 56 | 57 | /** 58 | * Send data to parent process. 59 | * @param {Buffer} data 60 | * @returns {Boolean} 61 | */ 62 | 63 | write(data) { 64 | if (global.postMessage.length === 2) { 65 | data.__proto__ = Uint8Array.prototype; 66 | global.postMessage({ data }, [data]); 67 | } else { 68 | global.postMessage(data.toString('hex')); 69 | } 70 | return true; 71 | } 72 | 73 | /** 74 | * Destroy the parent process. 75 | */ 76 | 77 | destroy() { 78 | global.close(); 79 | } 80 | } 81 | 82 | /* 83 | * Expose 84 | */ 85 | 86 | module.exports = Parent; 87 | -------------------------------------------------------------------------------- /lib/workers/parent.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parent.js - worker processes for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const EventEmitter = require('events'); 10 | 11 | /** 12 | * Parent 13 | * Represents the parent process. 14 | * @alias module:workers.Parent 15 | * @extends EventEmitter 16 | */ 17 | 18 | class Parent extends EventEmitter { 19 | /** 20 | * Create the parent process. 21 | * @constructor 22 | */ 23 | 24 | constructor() { 25 | super(); 26 | 27 | this.init(); 28 | } 29 | 30 | /** 31 | * Initialize master (node.js). 32 | * @private 33 | */ 34 | 35 | init() { 36 | process.stdin.on('data', (data) => { 37 | this.emit('data', data); 38 | }); 39 | 40 | // Nowhere to send these errors: 41 | process.stdin.on('error', () => {}); 42 | process.stdout.on('error', () => {}); 43 | process.stderr.on('error', () => {}); 44 | 45 | process.on('uncaughtException', (err) => { 46 | this.emit('exception', err); 47 | }); 48 | } 49 | 50 | /** 51 | * Send data to parent process. 52 | * @param {Buffer} data 53 | * @returns {Boolean} 54 | */ 55 | 56 | write(data) { 57 | return process.stdout.write(data); 58 | } 59 | 60 | /** 61 | * Destroy the parent process. 62 | */ 63 | 64 | destroy() { 65 | process.exit(0); 66 | } 67 | } 68 | 69 | /* 70 | * Expose 71 | */ 72 | 73 | module.exports = Parent; 74 | -------------------------------------------------------------------------------- /lib/workers/worker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * worker.js - worker thread/process for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * https://github.com/handshake-org/hsd 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const Master = require('./master'); 10 | const server = new Master(); 11 | 12 | process.title = 'hsd-worker'; 13 | 14 | server.listen(); 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hsd", 3 | "version": "7.99.0", 4 | "description": "Cryptocurrency bike-shed", 5 | "license": "MIT", 6 | "repository": "git://github.com/handshake-org/hsd.git", 7 | "homepage": "https://github.com/handshake-org/hsd", 8 | "bugs": { 9 | "url": "https://github.com/handshake-org/hsd/issues" 10 | }, 11 | "author": "Christopher Jeffrey ", 12 | "keywords": [ 13 | "blockchain", 14 | "cryptocurrency", 15 | "handshake", 16 | "hns", 17 | "wallet" 18 | ], 19 | "engines": { 20 | "node": ">=14.0.0" 21 | }, 22 | "dependencies": { 23 | "@handshake-org/bfilter": "~2.3.0", 24 | "bcfg": "~0.2.2", 25 | "bcrypto": "~5.4.0", 26 | "bcurl": "~0.2.1", 27 | "bdb": "~1.6.2", 28 | "bdns": "~0.1.5", 29 | "bevent": "~0.1.6", 30 | "bfile": "~0.2.3", 31 | "bheep": "~0.1.6", 32 | "binet": "~0.3.9", 33 | "blgr": "~0.2.1", 34 | "blru": "~0.1.8", 35 | "blst": "~0.1.6", 36 | "bmutex": "~0.1.7", 37 | "bns": "~0.15.0", 38 | "bsert": "~0.0.13", 39 | "bsock": "~0.1.11", 40 | "bsocks": "~0.2.6", 41 | "btcp": "~0.1.5", 42 | "buffer-map": "~0.0.8", 43 | "bufio": "~1.2.3", 44 | "bupnp": "~0.2.6", 45 | "bval": "~0.1.8", 46 | "bweb": "~0.2.0", 47 | "goosig": "~0.10.0", 48 | "n64": "~0.2.10", 49 | "urkel": "~1.0.3" 50 | }, 51 | "devDependencies": { 52 | "bmocha": "^2.2.1", 53 | "bslintrc": "^0.0.3" 54 | }, 55 | "main": "./lib/hsd.js", 56 | "bin": { 57 | "hsd": "./bin/hsd", 58 | "hsd-node": "./bin/node", 59 | "hsd-spvnode": "./bin/spvnode", 60 | "hs-seeder": "./bin/hs-seeder", 61 | "hs-wallet": "./bin/hsw", 62 | "hsd-cli": "./bin/hsd-cli", 63 | "hsd-rpc": "./bin/hsd-rpc", 64 | "hsw-cli": "./bin/hsw-cli", 65 | "hsw-rpc": "./bin/hsw-rpc" 66 | }, 67 | "scripts": { 68 | "build-docs": "jsdoc -c jsdoc.json", 69 | "lint": "eslint $(cat .eslintfiles)", 70 | "lint-file": "eslint", 71 | "test": "bmocha --reporter spec test/*.js", 72 | "test-browser": "NODE_BACKEND=js bmocha --reporter spec test/*.js", 73 | "test-file": "bmocha --reporter spec", 74 | "test-file-browser": "NODE_BACKEND=js bmocha --reporter spec", 75 | "test-ci": "nyc --reporter lcov bmocha -- --reporter spec test/*.js" 76 | }, 77 | "browser": { 78 | "./lib/covenants/reserved": "./lib/covenants/reserved-browser.js", 79 | "./lib/covenants/locked": "./lib/covenants/locked-browser.js", 80 | "./lib/hd/nfkd": "./lib/hd/nfkd-compat.js", 81 | "./lib/hd/wordlist": "./lib/hd/wordlist-browser.js", 82 | "./lib/workers/child": "./lib/workers/child-browser.js", 83 | "./lib/workers/parent": "./lib/workers/parent-browser.js", 84 | "./lib/hsd": "./lib/hsd-browser.js", 85 | "./lib/blockstore/index.js": "./lib/blockstore/index-browser.js" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/address-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const Address = require('../lib/primitives/address'); 5 | 6 | describe('Address', function() { 7 | it('should match mainnet p2pkh address', () => { 8 | const raw = '6d5571fdbca1019cd0f0cd792d1b0bdfa7651c7e'; 9 | const p2pkh = Buffer.from(raw, 'hex'); 10 | const addr = Address.fromPubkeyhash(p2pkh); 11 | const expect = 'hs1qd42hrldu5yqee58se4uj6xctm7nk28r70e84vx'; 12 | assert.strictEqual(addr.toString('main'), expect); 13 | }); 14 | 15 | it('should check standardness of address', () => { 16 | const addr = new Address(); 17 | 18 | // Standard p2wpkh 19 | addr.version = 0; 20 | addr.hash = Buffer.alloc(20); 21 | assert(!addr.isUnknown()); 22 | 23 | // Standard p2wsh 24 | addr.version = 0; 25 | addr.hash = Buffer.alloc(32); 26 | assert(!addr.isUnknown()); 27 | 28 | // nullData, any valid length 29 | for (let i = 2; i <= 40; i++) { 30 | addr.version = 31; 31 | addr.hash = Buffer.alloc(i); 32 | assert(!addr.isUnknown()); 33 | } 34 | 35 | // Non-Standard address 36 | addr.version = 0; 37 | addr.hash = Buffer.alloc(19); 38 | assert(addr.isUnknown()); 39 | 40 | addr.version = 0; 41 | addr.hash = Buffer.alloc(33); 42 | assert(addr.isUnknown()); 43 | 44 | // Undefined version 45 | addr.version = 1; 46 | addr.hash = Buffer.alloc(32); 47 | assert(addr.isUnknown()); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/block-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const Block = require('../lib/primitives/block'); 5 | const MemBlock = require('../lib/primitives/memblock'); 6 | 7 | describe('Block', function() { 8 | this.timeout(10000); 9 | 10 | describe('Serialization', function() { 11 | let block = null; 12 | let raw = null; 13 | 14 | it('should create a block from JSON', async () => { 15 | const prevBlock = 16 | '0000000000000000000000000000000000000000000000000000000000000000'; 17 | const merkleRoot = 18 | '0101010101010101010101010101010101010101010101010101010101010101'; 19 | const witnessRoot = 20 | '0202020202020202020202020202020202020202020202020202020202020202'; 21 | const treeRoot = 22 | '0303030303030303030303030303030303030303030303030303030303030303'; 23 | const reservedRoot = 24 | '0404040404040404040404040404040404040404040404040404040404040404'; 25 | const extraNonce = 26 | '050505050505050505050505050505050505050505050505'; 27 | const mask = 28 | '0606060606060606060606060606060606060606060606060606060606060606'; 29 | 30 | block = Block.fromJSON({ 31 | version: 1, 32 | prevBlock, 33 | merkleRoot, 34 | witnessRoot, 35 | treeRoot, 36 | reservedRoot, 37 | extraNonce, 38 | mask, 39 | time: 0, 40 | bits: 0, 41 | nonce: 0, 42 | txs: [ 43 | { 44 | version: 1, 45 | locktime: 0, 46 | inputs: [ 47 | { 48 | sequence: 0, 49 | prevout: { 50 | hash: Buffer.alloc(32, 7).toString('hex'), 51 | index: 0 52 | }, 53 | witness: [] 54 | } 55 | ], 56 | outputs: [ 57 | { 58 | value: 1234567, 59 | address: 'rs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqn6kda' 60 | } 61 | ] 62 | } 63 | ] 64 | }); 65 | 66 | assert.bufferEqual(block.prevBlock, Buffer.from(prevBlock, 'hex')); 67 | assert.bufferEqual(block.merkleRoot, Buffer.from(merkleRoot, 'hex')); 68 | assert.bufferEqual(block.witnessRoot, Buffer.from(witnessRoot, 'hex')); 69 | assert.bufferEqual(block.treeRoot, Buffer.from(treeRoot, 'hex')); 70 | assert.bufferEqual(block.reservedRoot, Buffer.from(reservedRoot, 'hex')); 71 | assert.bufferEqual(block.extraNonce, Buffer.from(extraNonce, 'hex')); 72 | assert.bufferEqual(block.mask, Buffer.from(mask, 'hex')); 73 | assert.strictEqual(block.version, 1); 74 | assert.strictEqual(block.time, 0); 75 | assert.strictEqual(block.bits, 0); 76 | assert.strictEqual(block.nonce, 0); 77 | assert.strictEqual(block.txs.length, 1); 78 | }); 79 | 80 | it('should deserialize and reserialze block', async () => { 81 | raw = block.encode(); 82 | const block2 = Block.decode(raw); 83 | assert.deepStrictEqual(block.toJSON(), block2.toJSON()); 84 | }); 85 | 86 | it('should create memblock from raw block', async () => { 87 | const memblock = MemBlock.decode(raw); 88 | assert.bufferEqual(block.hash(), memblock.hash()); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/coin-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {BufferWriter} = require('bufio'); 4 | const assert = require('bsert'); 5 | const nodejsUtil = require('util'); 6 | const random = require('bcrypto/lib/random'); 7 | 8 | const Coin = require('../lib/primitives/coin'); 9 | const KeyRing = require('../lib/primitives/keyring'); 10 | const common = require('../test/util/common'); 11 | const {types, typesByVal} = require('../lib/covenants/rules'); 12 | 13 | const tx1 = common.readTX('tx1'); 14 | const coin1 = common.readFile('coin1.raw'); 15 | 16 | describe('Coin', function() { 17 | it('should serialize and deserialize from JSON', () => { 18 | const key = KeyRing.generate(); 19 | const networks = ['main', 'testnet', 'regtest', 'simnet']; 20 | 21 | for (const network of networks) { 22 | const addr = key.getAddress().toString(network); 23 | const item = random.randomBytes(32); 24 | const json = { 25 | version: 0, 26 | height: 0, 27 | value: 1e4, 28 | address: addr, 29 | coinbase: true, 30 | covenant: { 31 | type: types.OPEN, 32 | action: typesByVal[types.OPEN], 33 | items: [item.toString('hex')] 34 | } 35 | }; 36 | 37 | const fromJSON = Coin.fromJSON(json, network); 38 | const bw = new BufferWriter(fromJSON.getSize()); 39 | fromJSON.write(bw); 40 | 41 | const coin = Coin.decode(bw.render()).getJSON(network); 42 | 43 | for (const [key, want] of Object.entries(json)) { 44 | const got = coin[key]; 45 | assert.deepEqual(want, got); 46 | } 47 | } 48 | }); 49 | 50 | it('should instantiate from tx', () => { 51 | const [tx] = tx1.getTX(); 52 | const json = require('./data/coin1.json'); 53 | const want = Coin.fromJSON(json); 54 | const got = Coin.fromTX(tx, 0, -1); 55 | 56 | assert.deepEqual(want.version, got.version); 57 | assert.deepEqual(want.height, got.height); 58 | assert.deepEqual(want.value, got.value); 59 | assert.deepEqual(want.address, got.address); 60 | assert.deepEqual(want.covenant, got.covenant); 61 | assert.deepEqual(want.coinbase, got.coinbase); 62 | assert.deepEqual(want.coinbase, got.coinbase); 63 | }); 64 | 65 | it('should instantiate from raw', () => { 66 | const json = require('./data/coin1.json'); 67 | const want = Coin.fromJSON(json); 68 | const got = Coin.decode(coin1); 69 | 70 | assert.deepEqual(want.version, got.version); 71 | assert.deepEqual(want.height, got.height); 72 | assert.deepEqual(want.value, got.value); 73 | assert.deepEqual(want.address, got.address); 74 | assert.deepEqual(want.covenant, got.covenant); 75 | assert.deepEqual(want.coinbase, got.coinbase); 76 | assert.deepEqual(want.coinbase, got.coinbase); 77 | }); 78 | 79 | it('should inspect Coin', () => { 80 | const coin = new Coin(); 81 | const fmt = nodejsUtil.format(coin); 82 | assert(typeof fmt === 'string'); 83 | assert(fmt.includes('version')); 84 | assert(fmt.includes('height')); 85 | assert(fmt.includes('value')); 86 | assert(fmt.includes('address')); 87 | assert(fmt.includes('covenant')); 88 | assert(fmt.includes('coinbase')); 89 | assert(fmt.includes('hash')); 90 | assert(fmt.includes('index')); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/coins-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const bio = require('bufio'); 5 | const CoinEntry = require('../lib/coins/coinentry'); 6 | const CoinView = require('../lib/coins/coinview'); 7 | const Input = require('../lib/primitives/input'); 8 | const Output = require('../lib/primitives/output'); 9 | const Outpoint = require('../lib/primitives/outpoint'); 10 | const common = require('./util/common'); 11 | 12 | const tx1 = common.readTX('tx1'); 13 | 14 | function reserialize(coin) { 15 | const raw = coin.encode(); 16 | const entry = CoinEntry.decode(raw); 17 | entry.raw = null; 18 | return CoinEntry.decode(entry.encode()); 19 | } 20 | 21 | function deepCoinsEqual(a, b) { 22 | assert.strictEqual(a.version, b.version); 23 | assert.strictEqual(a.height, b.height); 24 | assert.strictEqual(a.coinbase, b.coinbase); 25 | assert.strictEqual(a.value, b.value); 26 | assert.strictEqual(a.covenant, b.covenant); 27 | assert.strictEqual(a.address, b.address); 28 | assert.bufferEqual(a.raw, b.raw); 29 | } 30 | 31 | describe('Coins', function() { 32 | it('should instantiate coinview from tx', () => { 33 | const [tx] = tx1.getTX(); 34 | const hash = tx.hash(); 35 | const view = new CoinView(); 36 | const prevout = new Outpoint(hash, 0); 37 | const input = Input.fromOutpoint(prevout); 38 | 39 | view.addTX(tx, 1); 40 | 41 | const coins = view.get(hash); 42 | assert.strictEqual(coins.outputs.size, tx.outputs.length); 43 | 44 | const entry = coins.get(0); 45 | assert(entry); 46 | assert.strictEqual(entry.version, 0); 47 | assert.strictEqual(entry.height, 1); 48 | assert.strictEqual(entry.coinbase, false); 49 | assert.strictEqual(entry.raw, null); 50 | assert(entry.output instanceof Output); 51 | assert.strictEqual(entry.spent, false); 52 | 53 | const output = view.getOutputFor(input); 54 | assert(output); 55 | 56 | deepCoinsEqual(entry, reserialize(entry)); 57 | }); 58 | 59 | it('should spend an output', () => { 60 | const [tx] = tx1.getTX(); 61 | const hash = tx.hash(); 62 | const view = new CoinView(); 63 | 64 | view.addTX(tx, 1); 65 | 66 | const coins = view.get(hash); 67 | assert(coins); 68 | 69 | const length = coins.outputs.size; 70 | 71 | view.spendEntry(new Outpoint(hash, 0)); 72 | 73 | assert.strictEqual(view.get(hash), coins); 74 | 75 | const entry = coins.get(0); 76 | assert(entry); 77 | assert(entry.spent); 78 | 79 | deepCoinsEqual(entry, reserialize(entry)); 80 | assert.strictEqual(coins.outputs.size, length); 81 | 82 | assert.strictEqual(view.undo.items.length, 1); 83 | }); 84 | 85 | it('should handle coin view', () => { 86 | const [tx, view] = tx1.getTX(); 87 | 88 | const size = view.getSize(tx); 89 | const bw = bio.write(size); 90 | const raw = view.write(bw, tx).render(); 91 | const br = bio.read(raw); 92 | const res = CoinView.read(br, tx); 93 | const prev = tx.inputs[0].prevout; 94 | const coins = res.get(prev.hash); 95 | 96 | assert.strictEqual(coins.outputs.size, 1); 97 | assert.strictEqual(coins.get(1), null); 98 | deepCoinsEqual(coins.get(0), reserialize(coins.get(0))); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/consensus-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const consensus = require('../lib/protocol/consensus'); 5 | const BN = require('bcrypto/lib/bn.js'); 6 | 7 | describe('Consensus', function() { 8 | it('should calculate reward properly', () => { 9 | let height = 0; 10 | let total = 0; 11 | 12 | for (;;) { 13 | const reward = consensus.getReward(height, 170000); 14 | assert(reward <= consensus.COIN * 2000); 15 | total += reward; 16 | if (reward === 0) 17 | break; 18 | height++; 19 | } 20 | 21 | assert.strictEqual(height, 5270000); 22 | assert.strictEqual(total, 679999997790000); 23 | }); 24 | 25 | it('should verify proof-of-work', () => { 26 | const bits = 0x1900896c; 27 | 28 | const hash = Buffer.from( 29 | '0000000000000000348f8ef340a84844aaa09b067141ea6742991ab11b3f2b67', 30 | 'hex' 31 | ); 32 | 33 | assert(consensus.verifyPOW(hash, bits)); 34 | }); 35 | 36 | it('should convert bits to target', () => { 37 | const bits = 0x1900896c; 38 | const target = consensus.fromCompact(bits); 39 | const expected = new BN( 40 | '0000000000000000896c00000000000000000000000000000000000000000000', 41 | 'hex'); 42 | 43 | assert.strictEqual(target.toString('hex'), expected.toString('hex')); 44 | }); 45 | 46 | it('should convert target to bits', () => { 47 | const target = new BN( 48 | '0000000000000000896c00000000000000000000000000000000000000000000', 49 | 'hex'); 50 | 51 | const bits = consensus.toCompact(target); 52 | const expected = 0x1900896c; 53 | 54 | assert.strictEqual(bits, expected); 55 | }); 56 | 57 | it('should check version bit', () => { 58 | assert(consensus.hasBit(0x20000001, 0)); 59 | assert(!consensus.hasBit(0x20000000, 0)); 60 | assert(consensus.hasBit(0x10000001, 0)); 61 | assert(consensus.hasBit(0x20000003, 1)); 62 | assert(consensus.hasBit(0x20000003, 0)); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/contractstate-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const ContractState = require('../lib/mempool/contractstate'); 5 | const Network = require('../lib/protocol/network'); 6 | const MTX = require('../lib/primitives/mtx'); 7 | const Output = require('../lib/primitives/output'); 8 | const CoinView = require('../lib/coins/coinview'); 9 | const rules = require('../lib/covenants/rules'); 10 | const NameState = require('../lib/covenants/namestate'); 11 | const {types} = rules; 12 | 13 | const network = Network.get('regtest'); 14 | 15 | function nameContext(name, type) { 16 | const rawName = Buffer.from(name, 'ascii'); 17 | const nameHash = rules.hashName(rawName); 18 | 19 | const output = new Output(); 20 | const mtx = new MTX(); 21 | const ns = new NameState(); 22 | const view = new CoinView(); 23 | 24 | switch (type) { 25 | case types.OPEN: 26 | output.covenant.setOpen(nameHash, rawName); 27 | break; 28 | case types.BID: 29 | output.covenant.setBid(nameHash, 0, rawName, Buffer.alloc(32)); 30 | break; 31 | case types.REVEAL: 32 | output.covenant.setReveal(nameHash, 100, Buffer.alloc(32)); 33 | break; 34 | case types.UPDATE: { 35 | const data = Buffer.from('hello world', 'ascii'); 36 | output.covenant.setUpdate(nameHash, 100, data); 37 | break; 38 | } 39 | } 40 | 41 | mtx.outputs.push(output); 42 | ns.name = name; 43 | ns.nameHash = nameHash; 44 | ns.height = 1; // prevent null ns 45 | 46 | view.names.set(nameHash, ns); 47 | 48 | return [mtx, view]; 49 | } 50 | 51 | describe('Contract State', function() { 52 | const name = 'foo'; 53 | const rawName = Buffer.from(name, 'ascii'); 54 | const nameHash = rules.hashName(rawName); 55 | 56 | it('Should construct', () => { 57 | const contract = new ContractState(network); 58 | assert.ok(contract); 59 | 60 | // Requires a network 61 | assert.throws(() => new ContractState()); 62 | }); 63 | 64 | it('should track an open', () => { 65 | const contract = new ContractState(network); 66 | 67 | const [mtx, view] = nameContext(name, types.OPEN); 68 | contract.track(mtx, view); 69 | 70 | assert.ok(contract.opens.has(nameHash)); 71 | 72 | const opens = contract.opens.get(nameHash); 73 | assert.ok(opens.has(Buffer.from(mtx.txid(), 'hex'))); 74 | }); 75 | 76 | it('should track a bid', () => { 77 | const contract = new ContractState(network); 78 | 79 | const [mtx, view] = nameContext(name, types.BID); 80 | contract.track(mtx, view); 81 | 82 | assert.ok(contract.bids.has(nameHash)); 83 | 84 | const bids = contract.bids.get(nameHash); 85 | assert.ok(bids.has(Buffer.from(mtx.txid(), 'hex'))); 86 | }); 87 | 88 | it('should track a reveal', () => { 89 | const contract = new ContractState(network); 90 | 91 | const [mtx, view] = nameContext(name, types.REVEAL); 92 | contract.track(mtx, view); 93 | 94 | assert.ok(contract.reveals.has(nameHash)); 95 | 96 | const reveals = contract.reveals.get(nameHash); 97 | assert.ok(reveals.has(Buffer.from(mtx.txid(), 'hex'))); 98 | }); 99 | 100 | it('should track an update', () => { 101 | const contract = new ContractState(network); 102 | 103 | const [mtx, view] = nameContext(name, types.UPDATE); 104 | contract.track(mtx, view); 105 | 106 | assert.ok(contract.updates.has(nameHash)); 107 | 108 | const updates = contract.updates.get(nameHash); 109 | assert.ok(updates.has(Buffer.from(mtx.txid(), 'hex'))); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/data/README.md: -------------------------------------------------------------------------------- 1 | Multiple test vectors are based on Bitcoin Core. 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2009-2017 The Bitcoin Core developers 6 | Copyright (c) 2009-2017 Bitcoin Developers 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /test/data/block14361.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block14361.raw -------------------------------------------------------------------------------- /test/data/block34663.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block34663.raw -------------------------------------------------------------------------------- /test/data/block37831.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block37831.raw -------------------------------------------------------------------------------- /test/data/block45288-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block45288-undo.raw -------------------------------------------------------------------------------- /test/data/block45288.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block45288.raw -------------------------------------------------------------------------------- /test/data/block55737-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block55737-undo.raw -------------------------------------------------------------------------------- /test/data/block55737.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block55737.raw -------------------------------------------------------------------------------- /test/data/block56940-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block56940-undo.raw -------------------------------------------------------------------------------- /test/data/block56940.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block56940.raw -------------------------------------------------------------------------------- /test/data/block57955-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block57955-undo.raw -------------------------------------------------------------------------------- /test/data/block57955.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block57955.raw -------------------------------------------------------------------------------- /test/data/block64498-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block64498-undo.raw -------------------------------------------------------------------------------- /test/data/block64498.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block64498.raw -------------------------------------------------------------------------------- /test/data/block65302-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block65302-undo.raw -------------------------------------------------------------------------------- /test/data/block65302.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/block65302.raw -------------------------------------------------------------------------------- /test/data/coin1.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0, 3 | "height": -1, 4 | "value": 99997200, 5 | "address": "rs1qxps2ljf5604tgyz7pvecuq6twwt4k9qsxcd27y", 6 | "covenant": { 7 | "type": 0, 8 | "action": "NONE", 9 | "items": [] 10 | }, 11 | "coinbase": false, 12 | "hash": "dc73eb199e8a70f69de74aecc5a1233e3a9a6e14ea62d0a08b17aca0a7f916ad", 13 | "index": 0 14 | } 15 | -------------------------------------------------------------------------------- /test/data/coin1.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/coin1.raw -------------------------------------------------------------------------------- /test/data/faucet-proof.base64: -------------------------------------------------------------------------------- 1 | MAEAAAsk88I7Sy9q89bcBYyQgm1M22vxwC7++XJxyVdqpVvJ8oH32lPurCMb4gg+GRREgJ26Bd23tf1+pDvj8JpGugxrfzWtzF3cRzdXfR64/rndCh1ABd/yjvgKYEOB/yxgN/TTzYcaHLlI/CR33j3OmHfS+e0ktRSb4Yv+fdmBrzJ4XjzbnZcrYBfyhc4QgqN8wM3fuvkNOuviuZsAJYkc3hdngxVZFQX0qQg87SuVDFbUT2GicLlmSwxE3b4Wk2EKthfNrdSa/8r2d0qbA7dyYtSd5Q+IrBLly4N8E2UTweIc8I5xBB7ssWEDGb3VQfroHulv+D0OIINjky32tDnbKYesCsOXdfD+5vDp8dg288NsZacFIpmy6El/ri58E31liXkU2qyvcyA+V+E4wqkPE4CShHqBaAzAbSxddiHnFvAfAFsKPkkdrkOQBJuxX0ZlXjHj2jJTspzwEE9Z0MYwuu2HAAAgBAAU3IMpICLzdoj4PQF5aBqkkHXqaK8U+0f6AQAAAAAAFNyDKSAi83aI+D0BeWgapJB16miv/gDh9QUA 2 | -------------------------------------------------------------------------------- /test/data/merkle27032.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/merkle27032.raw -------------------------------------------------------------------------------- /test/data/merkle43269.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/merkle43269.raw -------------------------------------------------------------------------------- /test/data/migrations/README.md: -------------------------------------------------------------------------------- 1 | Migration data with before/after entries. 2 | 3 | It contains generator scripts and manually assembled jsons for the migrations. 4 | 5 | ## Notes 6 | - wallet-4-bid-reveal.json (wallet-4-bid-reveal-gen.js) 7 | - `fullAfter` is db dump with new version, `after` is filtered out things 8 | that can not be recovered. 9 | - Removed not-owned BID <-> Reveal bidings, they are not possible to 10 | recover. We don't have transactions for those. We can't recover height of 11 | the bids either. 12 | -------------------------------------------------------------------------------- /test/data/migrations/chain-0-migrate-migrations-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Logger = require('blgr'); 4 | const Network = require('../../../lib/protocol/network'); 5 | const ChainDB = require('../../../lib/blockchain/chaindb'); 6 | const mutils = require('../../util/migrations'); 7 | 8 | const NETWORK = Network.get('regtest'); 9 | let blockstore = null; 10 | 11 | try { 12 | blockstore = require('../../../lib/blockstore'); 13 | } catch (e) { 14 | ; 15 | } 16 | 17 | async function dumpMigration(options) { 18 | let blocks = null; 19 | 20 | if (blockstore && !options.spv) { 21 | blocks = blockstore.create({ 22 | memory: true, 23 | network: NETWORK, 24 | logger: Logger.global 25 | }); 26 | 27 | await blocks.open(); 28 | } 29 | 30 | const chainDB = new ChainDB({ 31 | logger: Logger.global, 32 | network: NETWORK, 33 | memory: true, 34 | prune: options.prune, 35 | spv: options.spv, 36 | entryCache: 5000, 37 | blocks 38 | }); 39 | 40 | await chainDB.open(); 41 | const data = await getMigrationDump(chainDB); 42 | 43 | await chainDB.close(); 44 | 45 | if (blocks) 46 | await blocks.close(); 47 | 48 | return data; 49 | } 50 | 51 | (async () => { 52 | const full = await dumpMigration({ prune: false, spv: false }); 53 | const prune = await dumpMigration({ prune: true, spv: false }); 54 | const spv = await dumpMigration({ prune: false, spv: true }); 55 | 56 | console.log(JSON.stringify({ 57 | full, 58 | prune, 59 | spv 60 | }, null, 2)); 61 | })().catch((err) => { 62 | console.error(err.stack); 63 | process.exit(1); 64 | }); 65 | 66 | async function getMigrationDump(chaindb) { 67 | const prefixes = [ 68 | 'O', 69 | 'M' 70 | ]; 71 | 72 | return mutils.dumpChainDB(chaindb, prefixes.map(mutils.prefix2hex)); 73 | } 74 | -------------------------------------------------------------------------------- /test/data/migrations/chain-4-migrationstate-v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Migration state update", 3 | "before": { 4 | "4d": "000000000000" 5 | }, 6 | "after": { 7 | "4d": "00000100010000" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/data/migrations/wallet-0-migrate-migrations-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * walletdb version 0 to 1 migration. 5 | * Final migration data needs modifications for 6 | * the correct next migration value. 7 | */ 8 | 9 | const Network = require('../../../lib/protocol/network'); 10 | const WalletDB = require('../../../lib/wallet/walletdb'); 11 | const mutils = require('../../util/migrations'); 12 | 13 | const NETWORK = Network.get('regtest'); 14 | 15 | (async () => { 16 | const wdb = new WalletDB({ 17 | network: NETWORK, 18 | memory: true 19 | }); 20 | 21 | await wdb.open(); 22 | console.log(JSON.stringify({ 23 | data: await getMigrationDump(wdb) 24 | }, null, 2)); 25 | 26 | await wdb.close(); 27 | })().catch((err) => { 28 | console.error(err.stack); 29 | process.exit(1); 30 | }); 31 | 32 | async function getMigrationDump(wdb) { 33 | const prefixes = [ 34 | 'M' 35 | ]; 36 | 37 | return mutils.dumpDB(wdb, prefixes.map(mutils.prefix2hex)); 38 | } 39 | -------------------------------------------------------------------------------- /test/data/migrations/wallet-0-migrate-migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Migrate migrations. Affects wdb layout M.", 3 | "cases": [ 4 | { 5 | "description": "Migration after migration flag was set.", 6 | "before": { 7 | "4d00000000": "00" 8 | }, 9 | "after": { 10 | "4d": "000000000200" 11 | } 12 | }, 13 | { 14 | "description": "Migration before flag was set.", 15 | "before": {}, 16 | "after": { 17 | "4d": "000000000200" 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test/data/migrations/wallet-2-account-lookahead-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This migration is for v4 -> v5. 5 | */ 6 | 7 | const bdb = require('bdb'); 8 | const Network = require('../../../lib/protocol/network'); 9 | const WalletDB = require('../../../lib/wallet/walletdb'); 10 | const wutils = require('../../util/wallet'); 11 | const mutils = require('../../util/migrations'); 12 | 13 | const layout = { 14 | wdb: { 15 | // W[wid] -> wallet id 16 | W: bdb.key('W', ['uint32']), 17 | 18 | // a[wid][index] -> account 19 | a: bdb.key('a', ['uint32', 'uint32']) 20 | } 21 | }; 22 | 23 | const NETWORK = Network.get('regtest'); 24 | const LOOKAHEAD1 = 10; 25 | const LOOKAHEAD2 = 200; 26 | 27 | const wallet1priv = 'rprvKE8qsHtkmUxUSPQdn2sFKFUcKyUQz9pKQhxjEWecnXg9hgJMsmJXcw' 28 | + 'J77SqmHT1R6mcuNqVPzgT2EoGStsXaUN92VJKhQWUB6uZdL8gAZvez'; 29 | const wallet2priv = 'rprvKE8qsHtkmUxUSR4jE7Lti9XV77hv7xxacAShw5MvxY6RfsAYVeB1WL' 30 | + 'WtjiebDmqTruVJxmMeQUMkk61e83WDZbZidDnNPhHyQpeEwxjuSZuG'; 31 | const rpub = 'rpubKBAPj83PkcGYSz3GR9xfzXBfTFTPtERb2x7fKvbikH9utGMvr6HDd4sTxt5zo' 32 | + 'arw4bzzgH1VDzBUoX9fotzmPrrngFyLMz3ozAi1ozAbJjSY'; 33 | 34 | (async () => { 35 | const wdb = new WalletDB({ 36 | network: NETWORK, 37 | memory: true 38 | }); 39 | 40 | await wdb.open(); 41 | 42 | const wallet1 = await wdb.create({ 43 | id: 'wallet1', 44 | master: wallet1priv, 45 | lookahead: LOOKAHEAD1 46 | }); 47 | 48 | await wallet1.createAccount({ 49 | name: 'alt', 50 | lookahead: LOOKAHEAD2 51 | }); 52 | 53 | const wallet2 = await wdb.create({ 54 | id: 'wallet2', 55 | master: wallet2priv, 56 | lookahead: LOOKAHEAD2, 57 | m: 1, 58 | n: 2 59 | }); 60 | 61 | await wallet2.addSharedKey(0, rpub); 62 | 63 | for (let i = 0; i < 100; i++) { 64 | const entry = wutils.nextEntry(wdb); 65 | await wdb.addBlock(entry, []); 66 | } 67 | 68 | console.log(JSON.stringify({ 69 | data: await getMigrationDump(wdb) 70 | }, null, 2)); 71 | 72 | await wdb.close(); 73 | })().catch((e) => { 74 | console.error(e); 75 | }); 76 | 77 | async function getMigrationDump(wdb) { 78 | const prefixes = []; 79 | 80 | // skip primary wallet. 81 | for (let i = 1; i < 3; i++) { 82 | prefixes.push(layout.wdb.W.encode(i).toString('hex')); 83 | prefixes.push(layout.wdb.a.encode(i, 0).slice(0, 5).toString('hex')); 84 | } 85 | 86 | return await mutils.dumpDB(wdb, prefixes); 87 | } 88 | -------------------------------------------------------------------------------- /test/data/migrations/wallet-2-account-lookahead.json: -------------------------------------------------------------------------------- 1 | { 2 | "before": { 3 | "5700000001": "0777616c6c657431", 4 | "5700000002": "0777616c6c657432", 5 | "610000000100000000": "0100010101000000010000000a03875c90a480000000fdc9691244b5dc0636ce0eabd905556c333e208b2815e463a6e65db29833c15d024a4400453207366ed8567bf78fdda5dccc1a0f8b1398f9925b744cc563575a7400", 6 | "610000000100000001": "010001010100000001000000c803875c90a480000001fc7d26586b3e6e082df9d01051258b8809960d5434d1abfe088ab507a0cfbbf003f44993e0ed4638fb5d32a272cca586ffe85040755d95568e01ca5c4845e27c9e00", 7 | "610000000200000000": "010101020100000001000000c8034a0c1e1180000000bda10baaccd9189fb3a56e2950ad198c45611cd3dbf0f154f699fa1c9ddc4b89022e05837732c62feb49896bc8b3cd8fd2f1b9b6bc415074ba31d858a810bbb29401032ac4ec93800000008921a0a732ac09e78b4f51464880ca9fc3e09a129564c0d8aef9fb1fd2ff175302f2b8aebdc25d17815420662ff8e2b9e24202a9b628f07ce2671cedac5f8001e1" 8 | 9 | }, 10 | "after": { 11 | "5700000001": "0777616c6c657431", 12 | "5700000002": "0777616c6c657432", 13 | "610000000100000000": "0100010101000000010000000a00000003875c90a480000000fdc9691244b5dc0636ce0eabd905556c333e208b2815e463a6e65db29833c15d024a4400453207366ed8567bf78fdda5dccc1a0f8b1398f9925b744cc563575a7400", 14 | "610000000100000001": "010001010100000001000000c800000003875c90a480000001fc7d26586b3e6e082df9d01051258b8809960d5434d1abfe088ab507a0cfbbf003f44993e0ed4638fb5d32a272cca586ffe85040755d95568e01ca5c4845e27c9e00", 15 | "610000000200000000": "010101020100000001000000c8000000034a0c1e1180000000bda10baaccd9189fb3a56e2950ad198c45611cd3dbf0f154f699fa1c9ddc4b89022e05837732c62feb49896bc8b3cd8fd2f1b9b6bc415074ba31d858a810bbb29401032ac4ec93800000008921a0a732ac09e78b4f51464880ca9fc3e09a129564c0d8aef9fb1fd2ff175302f2b8aebdc25d17815420662ff8e2b9e24202a9b628f07ce2671cedac5f8001e1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/data/migrations/wallet-6-migrationstate-v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Migration state update", 3 | "before": { 4 | "4d": "000000000000" 5 | }, 6 | "after": { 7 | "4d": "00000100010000" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/data/mnemonic-portuguese.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "00000000000000000000000000000000", 4 | "abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abater", 5 | "https://iancoleman.io/bip39/#portuguese", 6 | "310b9658971242b9192717c83c0f2656913d7d8028d7ec556e92c3c71168cc60a457b88cd1ee998871c3afb4dd6339375e5526e16eabb11142ad7e89033fff38", 7 | "xprv9s21ZrQH143K2QUuKjgvQqqeUvTM269XQLp9bq1PkUoJZjfzPPJRZtBTUY7BtqLGjSupBjcWcygkdrUc7xcBY12xfaBMGixnw83HRK11HiP" 8 | ], 9 | [ 10 | "7b299ca1eae745e03160702d45fab2231c930c9af1f55fd1d9a953a383a13104", 11 | "heresia dialeto borracha rugido ginasta travar pupilo alusivo caju cansado ofensiva debulhar rapadura ironizar forjar convite evasivo leigo flechada nervoso cidade resolver prometer jorrar", 12 | "https://iancoleman.io/bip39/#portuguese", 13 | "64f70e96ad2a77550e7108e493a077b8d15667b7800fd9c9384b073dd0dea61fdee8bf0e89afd9f9c273ce04c73292e37e724d78c12050dec452526ac44b505a", 14 | "xprv9s21ZrQH143K2nNrkwhyctoQ9kwvnzCGVakLm8LhzNHuE58tYsaHg7b6EV7NM2kDPutPu6JLxVMjGrVTr3YwUFDN2DtrNnFezwNA9V2H3kc" 15 | ], 16 | [ 17 | "2cd87f318e429cc2dc5b13682a3cb0c1", 18 | "caiaque projeto recuar axila brochura exemplo garrafa exposto ficar duplo cachorro inibido", 19 | "https://iancoleman.io/bip39/#portuguese", 20 | "32b1452637cac7201550d0b6012dba0f81fc03bb5b25902cafc80e0fbc87c6dcd10dc7524d14b21964f3a1ee2fd028994685a149b48859c5fd12f66ba6fb8208", 21 | "xprv9s21ZrQH143K3LF8znZ3QBkP4mckt88f67pVbJx9SRiBB9QownrK1M7Td3Z5kbPrps2nzMDjyNX97fujPr7SQKMEF4KQd5iyBDNjPbfrQRY" 22 | ], 23 | [ 24 | "2cd87f318e429cc2dc5b13682a3cb0c1", 25 | "caiaque projeto recuar axila brochura exemplo garrafa exposto ficar duplo cachorro inibido", 26 | "", 27 | "273fdf21a3bd9ee074e44eb2af868e1594290c12017027b8da434b83c5842b783900e79467c41664e152c67c71a1dd9be4c17de90a2d9ebe3d78095bbafbb24e", 28 | "xprv9s21ZrQH143K4QiuYZPvfS2vdcyL1FPE2tAYJfbAFR83ZLoYbMHNNRGcnbCuctbufgU4AmbujNMvWpPT49LBB7y8VY49bVCpyn7bp8ZQKhb" 29 | ] 30 | ] 31 | -------------------------------------------------------------------------------- /test/data/mtx1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "dc73eb199e8a70f69de74aecc5a1233e3a9a6e14ea62d0a08b17aca0a7f916ad", 3 | "witnessHash": "b3f4428bde7742db4c6a65b70a1a3f9b64c32cc1d061ca352db00606f9938bb4", 4 | "fee": 2800, 5 | "rate": 22764, 6 | "mtime": 1558044979, 7 | "version": 0, 8 | "inputs": [{ 9 | "prevout": { 10 | "hash": "135a7ab2b078101a4b5c74ccb13ec33aafbfe3cb08d69f79bb6a07183ed4be4d", 11 | "index": 0 12 | }, 13 | "witness": ["", "03253ea6d6486d1b9cc3ab01a9a321d65c350c6c26a9c536633e2ef36163316bf2"], 14 | "sequence": 4294967295, 15 | "coin": { 16 | "version": 0, 17 | "height": 2, 18 | "value": 2000000000, 19 | "address": "rs1q4rvs9pp9496qawp2zyqpz3s90fjfk362q92vq8", 20 | "covenant": { 21 | "type": 0, 22 | "action": "NONE", 23 | "items": [] 24 | }, 25 | "coinbase": true 26 | }, 27 | "path": { 28 | "name": "default", 29 | "account": 0, 30 | "change": false, 31 | "derivation": "m/44'/5355'/0'/0/0" 32 | } 33 | }], 34 | "outputs": [{ 35 | "value": 99997200, 36 | "address": "rs1qxps2ljf5604tgyz7pvecuq6twwt4k9qsxcd27y", 37 | "covenant": { 38 | "type": 0, 39 | "action": "NONE", 40 | "items": [] 41 | } 42 | }, { 43 | "value": 1900000000, 44 | "address": "rs1qsfcpg4w2qwzx6ht5aa30dj5vddwvkdfq82dlae", 45 | "covenant": { 46 | "type": 0, 47 | "action": "NONE", 48 | "items": [] 49 | } 50 | }], 51 | "locktime": 0, 52 | "hex": "0000000001135a7ab2b078101a4b5c74ccb13ec33aafbfe3cb08d69f79bb6a07183ed4be4d00000000ffffffff0210d6f5050000000000143060afc934d3eab4105e0b338e034b73975b1410000000b33f7100000000001482701455ca03846d5d74ef62f6ca8c6b5ccb352000000000000002002103253ea6d6486d1b9cc3ab01a9a321d65c350c6c26a9c536633e2ef36163316bf2" 53 | } 54 | -------------------------------------------------------------------------------- /test/data/mtx2.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "dc73eb199e8a70f69de74aecc5a1233e3a9a6e14ea62d0a08b17aca0a7f916ad", 3 | "witnessHash": "b3f4428bde7742db4c6a65b70a1a3f9b64c32cc1d061ca352db00606f9938bb4", 4 | "fee": 2800, 5 | "rate": 22764, 6 | "mtime": 1558044979, 7 | "version": 0, 8 | "inputs": [{ 9 | "prevout": { 10 | "hash": "135a7ab2b078101a4b5c74ccb13ec33aafbfe3cb08d69f79bb6a07183ed4be4d", 11 | "index": 0 12 | }, 13 | "witness": ["", "03253ea6d6486d1b9cc3ab01a9a321d65c350c6c26a9c536633e2ef36163316bf2"], 14 | "sequence": 4294967295, 15 | "coin": { 16 | "version": 0, 17 | "height": 2, 18 | "value": 2000000000, 19 | "address": "rs1q4rvs9pp9496qawp2zyqpz3s90fjfk362q92vq8", 20 | "covenant": { 21 | "type": 0, 22 | "action": "NONE", 23 | "items": [] 24 | }, 25 | "coinbase": true 26 | } 27 | }], 28 | "outputs": [{ 29 | "value": 99997200, 30 | "address": "rs1qxps2ljf5604tgyz7pvecuq6twwt4k9qsxcd27y", 31 | "covenant": { 32 | "type": 0, 33 | "action": "NONE", 34 | "items": [] 35 | } 36 | }, { 37 | "value": 1900000000, 38 | "address": "rs1qsfcpg4w2qwzx6ht5aa30dj5vddwvkdfq82dlae", 39 | "covenant": { 40 | "type": 0, 41 | "action": "NONE", 42 | "items": [] 43 | } 44 | }], 45 | "locktime": 0, 46 | "hex": "0000000001135a7ab2b078101a4b5c74ccb13ec33aafbfe3cb08d69f79bb6a07183ed4be4d00000000ffffffff0210d6f5050000000000143060afc934d3eab4105e0b338e034b73975b1410000000b33f7100000000001482701455ca03846d5d74ef62f6ca8c6b5ccb352000000000000002002103253ea6d6486d1b9cc3ab01a9a321d65c350c6c26a9c536633e2ef36163316bf2" 47 | } 48 | -------------------------------------------------------------------------------- /test/data/mtx3.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "91d91fd61a8d084d6404fe090747c6d7300760cb49e92b3f352c52737f1db9e1", 3 | "witnessHash": "1be8536363828117119f13061c41f006d01b9c5269a01d022a923fb99020e0ef", 4 | "fee": 3740, 5 | "rate": 20107, 6 | "mtime": 1649345186, 7 | "version": 0, 8 | "inputs": [ 9 | { 10 | "prevout": { 11 | "hash": "4c29777bacfb3ba55d530861217a903db3eb8028d47654cc525196f39df238c9", 12 | "index": 0 13 | }, 14 | "witness": [ 15 | "", 16 | "fbf6d36dc5b9b999d2c07b57d82cbcb686069c9e5b401fb12fcb4b713026d7d52c8e8961f2f4bd0426644d4c283cf7ec9d909c6f7034696f65d5a0e658cb5e6701", 17 | "bfb18cb3df786f2a5a627aa5e77272290830c579c7ba76e8fb8d9865b110921e3a82f436c427d691d53e8a9618595b62cd0da04ca8ad2f49574b30dd09b79dfc01", 18 | "52210204765910dd41e2318a601e8d79dbff730eef468e5a0f18b71fbde40753989f472102251d5cfc891654a394281c6a61fa6fe2b3cc3e925527d491280d52494253c9872102d3b610ab42c289f9353111337fc9af5cc23e3df4beecd8f4468a8a63ec0f51b553ae" 19 | ], 20 | "sequence": 4294967295, 21 | "coin": { 22 | "version": 0, 23 | "height": 406, 24 | "value": 2000000000, 25 | "address": "rs1qkndzr6cg3604h8hqp3qkg4l4llwm7vk5x02et2mmykns235zkcesc6xmc9", 26 | "covenant": { 27 | "type": 0, 28 | "action": "NONE", 29 | "items": [] 30 | }, 31 | "coinbase": true 32 | } 33 | } 34 | ], 35 | "outputs": [ 36 | { 37 | "value": 1000000, 38 | "address": "rs1qf3t02q76dr4pt70ejhynv7yyceacseaxkvp0nu", 39 | "covenant": { 40 | "type": 0, 41 | "action": "NONE", 42 | "items": [] 43 | } 44 | }, 45 | { 46 | "value": 1998996260, 47 | "address": "rs1q45u8mt679yps8l9jkuvsn02pyjmdqv4a8f3devmu7vkd70q0qghqv4saca", 48 | "covenant": { 49 | "type": 0, 50 | "action": "NONE", 51 | "items": [] 52 | } 53 | } 54 | ], 55 | "locktime": 0, 56 | "hex": "00000000014c29777bacfb3ba55d530861217a903db3eb8028d47654cc525196f39df238c900000000ffffffff0240420f000000000000144c56f503da68ea15f9f995c9367884c67b8867a6000024432677000000000020ad387daf5e290303fcb2b71909bd4124b6d032bd3a62dcb37cf32cdf3c0f022e000000000000040041fbf6d36dc5b9b999d2c07b57d82cbcb686069c9e5b401fb12fcb4b713026d7d52c8e8961f2f4bd0426644d4c283cf7ec9d909c6f7034696f65d5a0e658cb5e670141bfb18cb3df786f2a5a627aa5e77272290830c579c7ba76e8fb8d9865b110921e3a82f436c427d691d53e8a9618595b62cd0da04ca8ad2f49574b30dd09b79dfc016952210204765910dd41e2318a601e8d79dbff730eef468e5a0f18b71fbde40753989f472102251d5cfc891654a394281c6a61fa6fe2b3cc3e925527d491280d52494253c9872102d3b610ab42c289f9353111337fc9af5cc23e3df4beecd8f4468a8a63ec0f51b553ae" 57 | } -------------------------------------------------------------------------------- /test/data/mtx4.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "4518da07c8495ed4516f24428e2c24aa158e0293d081f7ef7041cbeb2728e315", 3 | "witnessHash": "42491fb69fa16a90c34aa84986ab9e4bd0061737a0c51407682c93df2055ec02", 4 | "fee": 10040, 5 | "rate": 45636, 6 | "mtime": 1649348358, 7 | "version": 0, 8 | "inputs": [ 9 | { 10 | "prevout": { 11 | "hash": "b744112694bd91fce21604b7f289475b83342c29f5d78f74b41568049b666601", 12 | "index": 0 13 | }, 14 | "witness": [ 15 | "", 16 | "f756b5f2607b40ac3b1bab60f8e1aa1577dbec55e8647437c93bf91101c49bc6645f4481e9f25c966b667d7ee06bfab0b9c85625d49fbd7b3f6c424d9ff3b32f01", 17 | "919973a460b3d06e39b08a59fdb176b157833345249aaa2fbd61cc2d4bbf6c465c67f14f2712a4a6b5ffc6a3207d90e4a607abdd4f34a663455ca2f69148f9c101", 18 | "c8e9adb07374ce91befff8d4c4f4c57c8250aeda9165947a920ffd7f616c84bd0d36c7e62849691866e89cf3a491384dcb23ea5a9ac18c0d14acb90b4b93ab4101", 19 | "5321028bc7fd60c184b66f748986c5911c1878d945acd73f0cc9730412fb6227a17e6b21036e01e69727a2d38b11b2d5bd80ba8b37c53ee26a0d53092637080e18a4d0b08b2103d26a0d09224184d42013d933456ffa03765f9a0e7bfbfa1bd89be79f48b4e4352103e3d0fbe96cf9ca9805bfd04a9c0f19f18d953f5a5a1365a104fb0b98c42d10b52103faabdd11a97a6bed004b0493199f234ac6af36a59ee504ca6547d1cfc550e21055ae" 20 | ], 21 | "sequence": 4294967295, 22 | "coin": { 23 | "version": 0, 24 | "height": 410, 25 | "value": 2000000000, 26 | "address": "rs1qtqtrjtpd736y3dm48nns7gj2ugquhyegr0k757tngpxx9pdxd6wsnfmtmr", 27 | "covenant": { 28 | "type": 0, 29 | "action": "NONE", 30 | "items": [] 31 | }, 32 | "coinbase": true 33 | } 34 | } 35 | ], 36 | "outputs": [ 37 | { 38 | "value": 1000000, 39 | "address": "rs1q2387n2xz0rdcp6eugurfnsmexf3s92eezdutye", 40 | "covenant": { 41 | "type": 0, 42 | "action": "NONE", 43 | "items": [] 44 | } 45 | }, 46 | { 47 | "value": 1998989960, 48 | "address": "rs1qvvq3kkwl4werdwe4e4y6g9zecpgrrydcamjwrfecf55vdd4lw76sag0e9f", 49 | "covenant": { 50 | "type": 0, 51 | "action": "NONE", 52 | "items": [] 53 | } 54 | } 55 | ], 56 | "locktime": 0, 57 | "hex": "0000000001b744112694bd91fce21604b7f289475b83342c29f5d78f74b41568049b66660100000000ffffffff0240420f00000000000014544fe9a8c278db80eb3c470699c379326302ab390000882a267700000000002063011b59dfabb236bb35cd49a41459c0503191b8eee4e1a7384d28c6b6bf77b5000000000000050041f756b5f2607b40ac3b1bab60f8e1aa1577dbec55e8647437c93bf91101c49bc6645f4481e9f25c966b667d7ee06bfab0b9c85625d49fbd7b3f6c424d9ff3b32f0141919973a460b3d06e39b08a59fdb176b157833345249aaa2fbd61cc2d4bbf6c465c67f14f2712a4a6b5ffc6a3207d90e4a607abdd4f34a663455ca2f69148f9c10141c8e9adb07374ce91befff8d4c4f4c57c8250aeda9165947a920ffd7f616c84bd0d36c7e62849691866e89cf3a491384dcb23ea5a9ac18c0d14acb90b4b93ab4101ad5321028bc7fd60c184b66f748986c5911c1878d945acd73f0cc9730412fb6227a17e6b21036e01e69727a2d38b11b2d5bd80ba8b37c53ee26a0d53092637080e18a4d0b08b2103d26a0d09224184d42013d933456ffa03765f9a0e7bfbfa1bd89be79f48b4e4352103e3d0fbe96cf9ca9805bfd04a9c0f19f18d953f5a5a1365a104fb0b98c42d10b52103faabdd11a97a6bed004b0493199f234ac6af36a59ee504ca6547d1cfc550e21055ae" 58 | } -------------------------------------------------------------------------------- /test/data/mtx6.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "bbd9cdbd26b3004f3d2dbf4ccd93c6192469712548ccca130174d8218deb0c50", 3 | "witnessHash": "88853967fc26c27e4da8bfce8ce66c2fd0886ee18b26b41573046138dde3f9dc", 4 | "fee": 2800, 5 | "rate": 20000, 6 | "mtime": 1649353354, 7 | "version": 0, 8 | "inputs": [ 9 | { 10 | "prevout": { 11 | "hash": "4da5191839b969e65ecf52de9921d3d47b10d6da4b825abac7621af34b1fcee5", 12 | "index": 0 13 | }, 14 | "witness": [ 15 | "08b0180768c1c314e41efaf42598938ea789433f0055d61eb876616de22b3a92733169e2f9ba502a197d75bc80757284adf7bcc9d3b30f2e6bf401fc0a4a229e01", 16 | "03ac979ef4f72ee40aa6d093f9ab082c96a82039da0a6a368eaab2d105f4001807" 17 | ], 18 | "sequence": 4294967295, 19 | "coin": { 20 | "version": 0, 21 | "height": 7, 22 | "value": 2000000000, 23 | "address": "rs1quuxahacfsamj2des3hq5nq7egee5jkmagd7xsq", 24 | "covenant": { 25 | "type": 0, 26 | "action": "NONE", 27 | "items": [] 28 | }, 29 | "coinbase": true 30 | } 31 | } 32 | ], 33 | "outputs": [ 34 | { 35 | "value": 1000000, 36 | "address": "rs1q9888z242wqc30xk8kzt8astw95mur0vsxmeg75", 37 | "covenant": { 38 | "type": 0, 39 | "action": "NONE", 40 | "items": [] 41 | } 42 | }, 43 | { 44 | "value": 1998997200, 45 | "address": "rs1qeeynqgqcsyh2tsauywjrla5sczpeztnhp9v2gf", 46 | "covenant": { 47 | "type": 0, 48 | "action": "NONE", 49 | "items": [] 50 | } 51 | } 52 | ], 53 | "locktime": 0, 54 | "hex": "00000000014da5191839b969e65ecf52de9921d3d47b10d6da4b825abac7621af34b1fcee500000000ffffffff0240420f0000000000001429ce712aaa7031179ac7b0967ec16e2d37c1bd900000d0462677000000000014ce49302018812ea5c3bc23a43ff690c083912e77000000000000024108b0180768c1c314e41efaf42598938ea789433f0055d61eb876616de22b3a92733169e2f9ba502a197d75bc80757284adf7bcc9d3b30f2e6bf401fc0a4a229e012103ac979ef4f72ee40aa6d093f9ab082c96a82039da0a6a368eaab2d105f4001807" 55 | } -------------------------------------------------------------------------------- /test/data/ns-icann.json: -------------------------------------------------------------------------------- 1 | { 2 | "com.": { 3 | "id": 0, 4 | "opcode": "QUERY", 5 | "code": "NOERROR", 6 | "qr": true, 7 | "aa": false, 8 | "tc": false, 9 | "rd": false, 10 | "ra": false, 11 | "z": false, 12 | "ad": true, 13 | "cd": false, 14 | "question": [ 15 | { 16 | "name": "com.", 17 | "class": "IN", 18 | "type": "NS" 19 | } 20 | ], 21 | "answer": [ 22 | ], 23 | "authority": [ 24 | { 25 | "name": "com.", 26 | "ttl": 172800, 27 | "class": "IN", 28 | "type": "NS", 29 | "data": { 30 | "ns": "a.gtld-servers.net." 31 | } 32 | }, 33 | { 34 | "name": "com.", 35 | "ttl": 86400, 36 | "class": "IN", 37 | "type": "DS", 38 | "data": { 39 | "keyTag": 30909, 40 | "algorithm": 8, 41 | "digestType": 2, 42 | "digest": "e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766", 43 | "algName": "RSASHA256", 44 | "hashName": "SHA256" 45 | } 46 | } 47 | ], 48 | "additional": [ 49 | { 50 | "name": "a.gtld-servers.net.", 51 | "ttl": 172800, 52 | "class": "IN", 53 | "type": "A", 54 | "data": { 55 | "address": "192.5.6.30" 56 | } 57 | } 58 | ] 59 | }, 60 | "cf.": { 61 | "id":0, 62 | "opcode":"QUERY", 63 | "code":"NOERROR", 64 | "qr":true, 65 | "aa":false, 66 | "tc":false, 67 | "rd":false, 68 | "ra":false, 69 | "z":false, 70 | "ad":true, 71 | "cd":false, 72 | "question":[ 73 | { 74 | "name":"cf.", 75 | "class":"IN", 76 | "type":"NS" 77 | } 78 | ], 79 | "answer":[ 80 | ], 81 | "authority":[ 82 | { 83 | "name":"cf.", 84 | "ttl":172800, 85 | "class":"IN", 86 | "type":"NS", 87 | "data":{ 88 | "ns":"a.ns.cf." 89 | } 90 | }, 91 | { 92 | "name":"cf.", 93 | "ttl":86400, 94 | "class":"IN", 95 | "type":"NSEC", 96 | "data":{ 97 | "nextDomain":"cfa.", 98 | "typeBitmap":[ 99 | 2, 100 | 46, 101 | 47 102 | ] 103 | } 104 | } 105 | ], 106 | "additional":[ 107 | { 108 | "name":"a.ns.cf.", 109 | "ttl":172800, 110 | "class":"IN", 111 | "type":"A", 112 | "data":{ 113 | "address":"185.21.168.17" 114 | } 115 | } 116 | ] 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/data/ns-names.json: -------------------------------------------------------------------------------- 1 | { 2 | "0c452105532473920739ba0da9ede7f1da8c8eb9c23fd089be97a486ee23bdc9": 3 | { 4 | "name": "proofofconcept", 5 | "records": [ 6 | { 7 | "type": "GLUE4", 8 | "ns": "ns.proofofconcept.", 9 | "address": "127.0.0.1" 10 | }, 11 | { 12 | "type": "TXT", 13 | "txt": [ 14 | "A simple social network for Handshake" 15 | ] 16 | }, 17 | { 18 | "type": "DS", 19 | "keyTag": 17552, 20 | "algorithm": 8, 21 | "digestType": 2, 22 | "digest": "bbbe70af5cd965360442cbef40e3299344af493d339592b93daa29f7839c1d58" 23 | } 24 | ] 25 | }, 26 | "b92ad996982b44fbea27d833c52e3fb0d6192d63835a13c61dfeb0126e2ee2ef": 27 | { 28 | "name": "nb", 29 | "records": [ 30 | { 31 | "type": "GLUE4", 32 | "ns": "ns1.nb.", 33 | "address": "127.0.0.1" 34 | }, 35 | { 36 | "type": "NS", 37 | "ns": "ns1.nb." 38 | } 39 | ] 40 | }, 41 | "bc288c7f5acb52989a274384dc65e49872b13f9ef9be9dc26f398e1b3c0df356": 42 | { 43 | "name": "schematic", 44 | "records": [ 45 | { 46 | "type": "TXT", 47 | "txt": [ 48 | "a text record" 49 | ] 50 | } 51 | ] 52 | }, 53 | "1478e0b76739f400397c4315aa7eb10ad0b45116b3ccf3b8b735345c23f56091": 54 | { 55 | "name": "empty-name", 56 | "records": [] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/data/tx1-undo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/tx1-undo.raw -------------------------------------------------------------------------------- /test/data/tx1.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handshake-org/hsd/85a1ada09b3aa3875666103a94311e3f9a880a22/test/data/tx1.raw -------------------------------------------------------------------------------- /test/headers-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Headers = require('../lib/primitives/headers'); 4 | const assert = require('bsert'); 5 | 6 | describe('Headers', function() { 7 | it('should match headers size', () => { 8 | const headers = new Headers(); 9 | 10 | assert.strictEqual(headers.getSize(), 236); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/input-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const MTX = require('../lib/primitives/mtx'); 5 | 6 | const mtx1json = require('./data/mtx1.json'); 7 | const mtx2json = require('./data/mtx2.json'); 8 | const mtx1 = MTX.fromJSON(mtx1json); 9 | const mtx2 = MTX.fromJSON(mtx2json); 10 | 11 | describe('MTX', function() { 12 | it('should serialize path', () => { 13 | const input = mtx1.inputs[0]; 14 | const view = mtx1.view; 15 | const coin = view.getCoinFor(input); 16 | const path = view.getPathFor(input); 17 | const json = input.getJSON('regtest', coin, path); 18 | const got = json.path; 19 | const want = { 20 | name: 'default', 21 | account: 0, 22 | change: false, 23 | derivation: 'm/44\'/5355\'/0\'/0/0' 24 | }; 25 | 26 | assert.deepStrictEqual(got, want); 27 | }); 28 | 29 | it('should not serialize path', () => { 30 | const input = mtx2.inputs[0]; 31 | const view = mtx2.view; 32 | const coin = view.getCoinFor(input); 33 | const path = view.getPathFor(input); 34 | const json = input.getJSON('regtest', coin, path); 35 | const got = json.path; 36 | const want = undefined; 37 | 38 | assert.deepStrictEqual(got, want); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/mnemonic-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const Mnemonic = require('../lib/hd/mnemonic'); 5 | const HDPrivateKey = require('../lib/hd/private'); 6 | 7 | const tests = { 8 | english: require('./data/mnemonic-english.json'), 9 | japanese: require('./data/mnemonic-japanese.json'), 10 | portuguese: require('./data/mnemonic-portuguese.json') 11 | }; 12 | 13 | describe('Mnemonic', function() { 14 | for (const language of Object.keys(tests)) { 15 | const test = tests[language]; 16 | let i = 0; 17 | 18 | for (const data of test) { 19 | const entropy = Buffer.from(data[0], 'hex'); 20 | const phrase = data[1]; 21 | const passphrase = data[2]; 22 | const seed = Buffer.from(data[3], 'hex'); 23 | const xpriv = data[4]; 24 | 25 | it(`should create a ${language} mnemonic from entropy (${i})`, () => { 26 | const mnemonic = new Mnemonic({ 27 | language, 28 | entropy 29 | }); 30 | 31 | assert.strictEqual(mnemonic.getPhrase(), phrase); 32 | assert.bufferEqual(mnemonic.getEntropy(), entropy); 33 | assert.bufferEqual(mnemonic.toSeed(passphrase), seed); 34 | 35 | const key = HDPrivateKey.fromMnemonic(mnemonic, passphrase); 36 | assert.strictEqual(key.toBase58('main'), xpriv); 37 | }); 38 | 39 | it(`should create a ${language} mnemonic from phrase (${i})`, () => { 40 | const mnemonic = new Mnemonic({ 41 | language, 42 | phrase 43 | }); 44 | 45 | assert.strictEqual(mnemonic.getPhrase(), phrase); 46 | assert.bufferEqual(mnemonic.getEntropy(), entropy); 47 | assert.bufferEqual(mnemonic.toSeed(passphrase), seed); 48 | 49 | const key = HDPrivateKey.fromMnemonic(mnemonic, passphrase); 50 | assert.strictEqual(key.toBase58('main'), xpriv); 51 | }); 52 | 53 | i += 1; 54 | } 55 | } 56 | 57 | it('should verify phrase', () => { 58 | const m1 = new Mnemonic(); 59 | const m2 = Mnemonic.fromPhrase(m1.getPhrase()); 60 | assert.bufferEqual(m2.getEntropy(), m1.getEntropy()); 61 | assert.strictEqual(m2.bits, m1.bits); 62 | assert.strictEqual(m2.language, m1.language); 63 | assert.bufferEqual(m2.toSeed(), m1.toSeed()); 64 | }); 65 | 66 | it('should standardize phrase', () => { 67 | for (const language of Object.keys(tests)) { 68 | const test = tests[language]; 69 | 70 | // modified phrase contains new lines before every space 71 | const phrase = test[0][1]; 72 | const phraseWithNl = phrase.replace(/(\s|\u3000)/g, '\n$&'); 73 | 74 | // ensure new lines are added 75 | assert(!phrase.includes('\n')); 76 | assert(phraseWithNl.includes('\n')); 77 | 78 | const m1 = Mnemonic.fromPhrase(phrase); 79 | const m2 = Mnemonic.fromPhrase(phraseWithNl); 80 | 81 | // Both mnemonics should have same phrase without new lines 82 | assert(m1.getPhrase() === m2.getPhrase()); 83 | assert(!m1.getPhrase().includes('\n')); 84 | } 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/net-lookup-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const Network = require('../lib/protocol/network'); 5 | const {lookup, resolve} = require('../lib/net/lookup'); 6 | 7 | const main = Network.get('main'); 8 | 9 | const notAHost = 'not-a-domain.not-a-domain'; 10 | 11 | describe.skip('Lookup', function() { 12 | this.timeout(10000); 13 | it('should lookup seed', async () => { 14 | for (const host of main.seeds) { 15 | const addresses = await lookup(host); 16 | assert(addresses.length > 0, 'addresses not found.'); 17 | } 18 | }); 19 | 20 | it('should fail lookup', async () => { 21 | let err; 22 | 23 | try { 24 | await lookup(notAHost); 25 | } catch (e) { 26 | err = e; 27 | } 28 | 29 | assert(err); 30 | assert.strictEqual(err.message, 'No DNS results.'); 31 | }); 32 | 33 | it('should lookup seed', async () => { 34 | for (const host of main.seeds) { 35 | const addresses = await resolve(host); 36 | 37 | assert(addresses.length > 0, 'addresses not found.'); 38 | } 39 | }); 40 | 41 | it('should fail resolve', async () => { 42 | let err; 43 | 44 | try { 45 | await resolve(notAHost); 46 | } catch (e) { 47 | err = e; 48 | } 49 | 50 | assert(err); 51 | assert.strictEqual(err.message, `Query error: NXDOMAIN (${notAHost} A).`); 52 | 53 | // TODO: Host that does not have A/AAAA records? 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/node-spv-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const SPVNode = require('../lib/node/spvnode'); 5 | const rules = require('../lib/covenants/rules'); 6 | 7 | describe('SPV Node', function() { 8 | describe('Filter update', function() { 9 | const node = new SPVNode({ 10 | memory: true, 11 | network: 'regtest', 12 | plugins: [require('../lib/wallet/plugin')] 13 | }); 14 | 15 | const pool = node.pool; 16 | const {wdb} = node.require('walletdb'); 17 | let wallet; 18 | 19 | const name1 = 'control'; // never add 20 | const hash1 = rules.hashName(name1); 21 | const name2 = 'lettuce'; 22 | const hash2 = rules.hashName(name2); 23 | const name3 = 'tomato'; 24 | const hash3 = rules.hashName(name3); 25 | 26 | // This function normally calls 27 | // peer.sendFilterLoad(this.spvFilter) 28 | // for each peer in pool. 29 | // This stub hands us the filter directly without any p2p connections. 30 | pool.sendFilterLoad = () => { 31 | pool.emit('filter load', pool.spvFilter); 32 | }; 33 | 34 | before(async () => { 35 | await node.open(); 36 | wallet = await wdb.get('primary'); 37 | }); 38 | 39 | after(async () => { 40 | await node.close(); 41 | }); 42 | 43 | it('should test false for all names', () => { 44 | assert(!wdb.filter.test(hash1)); 45 | assert(!wdb.filter.test(hash2)); 46 | assert(!wdb.filter.test(hash3)); 47 | assert(!pool.spvFilter.test(hash1)); 48 | assert(!pool.spvFilter.test(hash2)); 49 | assert(!pool.spvFilter.test(hash3)); 50 | }); 51 | 52 | it('should import name (wallet) and update filter', async () => { 53 | const waiter = new Promise((resolve) => { 54 | pool.once('filter load', (filter) => { 55 | resolve(filter); 56 | }); 57 | }); 58 | await wallet.importName(name2); 59 | 60 | // WalletDB filter has added name 61 | assert(!wdb.filter.test(hash1)); 62 | assert(wdb.filter.test(hash2)); 63 | assert(!wdb.filter.test(hash3)); 64 | 65 | // Filter sent from pool has also added name 66 | const filter = await waiter; 67 | assert(!filter.test(hash1)); 68 | assert(filter.test(hash2)); 69 | assert(!filter.test(hash3)); 70 | }); 71 | 72 | it('should watch name (pool) and update filter again', async () => { 73 | const waiter = new Promise((resolve) => { 74 | pool.once('filter load', (filter) => { 75 | resolve(filter); 76 | }); 77 | }); 78 | await pool.watchName(hash3); 79 | 80 | // Filter sent from pool has added name 81 | const filter = await waiter; 82 | assert(!filter.test(hash1)); 83 | assert(filter.test(hash2)); 84 | assert(filter.test(hash3)); 85 | 86 | // ...so has walletDB filter 87 | // (seems backwards, but the filters are the same literal object) 88 | assert(!wdb.filter.test(hash1)); 89 | assert(wdb.filter.test(hash2)); 90 | assert(wdb.filter.test(hash3)); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/ownership-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const {ownership} = require('../lib/covenants/ownership'); 5 | const Address = require('../lib/primitives/address'); 6 | const Network = require('../lib/protocol/network'); 7 | const network = Network.get('regtest'); 8 | 9 | describe('Ownership', function() { 10 | it('should encode / decode ownership TXT data', () => { 11 | const block1Hash = 12 | '0025f4480dadc61f13f507af9c9c9d06373fed38727ea467b6b2d5b09d522164'; 13 | const claim = { 14 | name: 'bitcoin', 15 | target: 'bitcoin.com.', 16 | value: 503513487, 17 | size: 4194, 18 | fee: 20960, 19 | address: 'rs1qjvvwkq2hq3cz5kmgz80v0a2l625ktdjtn60dpw', 20 | txt: 'hns-regtest:aakjgghlaflqi4bklnubdxwh6vp5fklfwzf73ycraa' + 21 | 's7isanvxdb6e7va6xzzhe5ay3t73jyoj7kiz5wwlk3bhksefsacaaaaafk5pvj' 22 | }; 23 | 24 | const claimAddress = Address.fromString(claim.address, 'regtest'); 25 | const decoded = ownership.decodeData(claim.txt, 'regtest'); 26 | assert.strictEqual(decoded.address.version, claimAddress.version); 27 | assert.strictEqual(decoded.address.hash, claimAddress.hash.toString('hex')); 28 | assert.strictEqual(decoded.fee, claim.fee); 29 | assert.strictEqual(decoded.commitHash, block1Hash); 30 | assert.strictEqual(decoded.commitHeight, 1); 31 | 32 | // (address, fee, commitHash, commitHeight, network) 33 | const encoded = ownership.createData( 34 | claimAddress, 35 | claim.fee, 36 | Buffer.from(block1Hash, 'hex'), 37 | 1, 38 | network 39 | ); 40 | assert.strictEqual(encoded, claim.txt); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/path-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const MTX = require('../lib/primitives/mtx'); 5 | const Path = require('../lib/wallet/path'); 6 | 7 | const mtx1json = require('./data/mtx1.json'); 8 | const mtx1 = MTX.fromJSON(mtx1json); 9 | 10 | describe('MTX', function() { 11 | it('should serialize path', () => { 12 | const input = mtx1.inputs[0]; 13 | const view = mtx1.view; 14 | const path = view.getPathFor(input); 15 | 16 | { 17 | const got = path.getJSON(); 18 | const want = { 19 | name: 'default', 20 | account: 0, 21 | change: false, 22 | derivation: 'm/0\'/0/0' 23 | }; 24 | 25 | assert.deepStrictEqual(got, want); 26 | } 27 | 28 | { 29 | const got = path.getJSON('regtest'); 30 | const want = { 31 | name: 'default', 32 | account: 0, 33 | change: false, 34 | derivation: 'm/44\'/5355\'/0\'/0/0' 35 | }; 36 | 37 | assert.deepStrictEqual(got, want); 38 | } 39 | }); 40 | 41 | it('should deserialize path', () => { 42 | const path1 = Path.fromJSON({ 43 | name: 'default', 44 | account: 0, 45 | change: true, 46 | derivation: 'm/0\'/1/1' 47 | }); 48 | 49 | const path2 = new Path().fromJSON({ 50 | name: 'default', 51 | account: 0, 52 | change: true, 53 | derivation: 'm/44\'/5355\'/0\'/1/1' 54 | }); 55 | 56 | assert.deepStrictEqual(path1, path2); 57 | 58 | const got = path1; 59 | const want = new Path(); 60 | want.name = 'default'; 61 | want.account = 0; 62 | want.branch = 1; 63 | want.index = 1; 64 | 65 | assert.deepStrictEqual(got, want); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/script-introspection-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const Address = require('../lib/primitives/address'); 5 | const Script = require('../lib/script/script'); 6 | const Witness = require('../lib/script/witness'); 7 | const Opcode = require('../lib/script/opcode'); 8 | const MTX = require('../lib/primitives/mtx'); 9 | const Coin = require('../lib/primitives/coin'); 10 | const Output = require('../lib/primitives/output'); 11 | const Covenant = require('../lib/primitives/covenant'); 12 | 13 | describe('Script transaction introspection', function() { 14 | describe('OP_TYPE', function() { 15 | it('should verify covenant type from TX output', async() => { 16 | for (let t = 0; t < 10; t++) { 17 | const script = new Script([ 18 | Opcode.fromSymbol('OP_TYPE'), 19 | Opcode.fromInt(t), 20 | Opcode.fromSymbol('OP_EQUAL') 21 | ]); 22 | 23 | const coin = Coin.fromOptions({ 24 | value: 1000, 25 | address: Address.fromScript(script) 26 | }); 27 | 28 | const witness = new Witness([script.encode()]); 29 | 30 | const output = new Output(); 31 | output.covenant = new Covenant(t, []); 32 | 33 | const mtx = new MTX(); 34 | mtx.addCoin(coin); 35 | mtx.inputs[0].witness.fromStack(witness); 36 | mtx.outputs[0] = output; 37 | 38 | assert(mtx.verify()); 39 | } 40 | }); 41 | 42 | it('should reject wrong covenant type from TX output', async() => { 43 | for (let t = 0; t < 10; t++) { 44 | const script = new Script([ 45 | Opcode.fromSymbol('OP_TYPE'), 46 | Opcode.fromInt(t), 47 | Opcode.fromSymbol('OP_EQUAL') 48 | ]); 49 | 50 | const coin = Coin.fromOptions({ 51 | value: 1000, 52 | address: Address.fromScript(script) 53 | }); 54 | 55 | const witness = new Witness([script.encode()]); 56 | 57 | const output = new Output(); 58 | output.covenant = new Covenant(t + 1, []); 59 | 60 | const mtx = new MTX(); 61 | mtx.addCoin(coin); 62 | mtx.inputs[0].witness.fromStack(witness); 63 | mtx.outputs[0] = output; 64 | 65 | assert(!mtx.verify()); 66 | } 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/txmeta-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const Network = require('../lib/protocol/network'); 5 | const TXMeta = require('../lib/primitives/txmeta'); 6 | 7 | const network = Network.get('regtest'); 8 | 9 | describe('TXMeta', function() { 10 | it('should return correct confirmations', async () => { 11 | // unconfirmed at height 100 12 | const txmeta1 = new TXMeta(); 13 | const txJSON1 = txmeta1.getJSON(network, null, 100); 14 | assert.strictEqual(txJSON1.confirmations, 0); 15 | 16 | // confirmed once at height 100 17 | const txmeta2 = TXMeta.fromOptions( {height: 100} ); 18 | txmeta2.height = 100; 19 | const txJSON2 = txmeta2.getJSON(network, null, 100); 20 | assert.strictEqual(txJSON2.confirmations, 1); 21 | }); 22 | 23 | it('should return blockhash as string', async () => { 24 | // confirmed once at height 100 25 | const block = Buffer.from('058f5cf9187d9f60729245956688022474ffc0eda80df6340a2053d5c4d149af'); 26 | const txmeta2 = TXMeta.fromOptions({ block }); 27 | const txJSON2 = txmeta2.getJSON(network, null, null); 28 | 29 | assert.strictEqual(txJSON2.block, block.toString('hex')); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/util/pagination.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const {forEventCondition} = require('./common'); 5 | 6 | exports.generateInitialBlocks = async (options) => { 7 | const { 8 | nodeCtx, 9 | coinbase, 10 | sendTXs, 11 | singleAccount, 12 | genesisTime 13 | } = options; 14 | 15 | const blockInterval = 600; 16 | const timewrap = 3200; 17 | 18 | async function mineBlock(coinbase, wrap = false) { 19 | const height = nodeCtx.height; 20 | let blocktime = genesisTime + (height + 1) * blockInterval; 21 | 22 | if (wrap && height % 5) 23 | blocktime -= timewrap; 24 | 25 | await nodeCtx.nclient.execute('setmocktime', [blocktime]); 26 | 27 | const blocks = await nodeCtx.mineBlocks(1, coinbase); 28 | const firstHash = blocks[0].hash().toString('hex'); 29 | const block = await nodeCtx.nclient.execute('getblock', [firstHash]); 30 | 31 | assert(block.time <= blocktime + 1); 32 | assert(block.time >= blocktime); 33 | 34 | return block; 35 | } 36 | 37 | let c = 0; 38 | 39 | // Establish baseline block interval for a median time 40 | for (; c < 11; c++) 41 | await mineBlock(coinbase); 42 | 43 | const h20 = entry => entry.height === 20; 44 | const walletEvents = forEventCondition(nodeCtx.wdb, 'block connect', h20); 45 | 46 | for (; c < 20; c++) 47 | await mineBlock(coinbase, true); 48 | 49 | await walletEvents; 50 | 51 | // 20 blocks * (20 txs per wallet, 19 default + 1 single account) 52 | for (; c < 40; c++) { 53 | await sendTXs(19); 54 | await sendTXs(1, singleAccount); 55 | await mineBlock(coinbase, true); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /test/util/stub.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const path = require('path'); 5 | const {StubResolver, wire} = require('bns'); 6 | const fs = require('bfile'); 7 | const {tmpdir} = require('os'); 8 | 9 | let CACHE = {}; 10 | 11 | /** 12 | * Proxy requests if they are not cached. 13 | */ 14 | 15 | class CachedStubResolver extends StubResolver { 16 | constructor(options) { 17 | super(options); 18 | 19 | this.enabled = true; 20 | this.cacheOnDisk = process.env['HSD_TEST_DNS_FILE_CACHE'] === 'true'; 21 | this.cacheDir = path.join(tmpdir(), 'hsd-test'); 22 | this.cacheFile = path.join(this.cacheDir, 'dns-cache.json'); 23 | 24 | this.loadCacheSync(); 25 | } 26 | 27 | loadCacheSync() { 28 | if (!this.cacheOnDisk) 29 | return; 30 | 31 | if (!fs.existsSync(this.cacheDir)) 32 | fs.mkdirSync(this.cacheDir); 33 | 34 | if (fs.existsSync(this.cacheFile)) 35 | CACHE = JSON.parse(fs.readFileSync(this.cacheFile, 'utf8')); 36 | } 37 | 38 | saveCacheSync() { 39 | if (!this.cacheOnDisk) 40 | return; 41 | 42 | const stringified = JSON.stringify(CACHE, null, 2); 43 | fs.writeFileSync(this.cacheFile, stringified, 'utf8'); 44 | } 45 | 46 | setCache(qs, res) { 47 | if (!this.enabled) 48 | return; 49 | 50 | assert(qs instanceof wire.Question); 51 | assert(res instanceof wire.Message); 52 | 53 | CACHE[qs.toString()] = res.toString(); 54 | this.saveCacheSync(); 55 | } 56 | 57 | hasCache(qs) { 58 | if (!this.enabled) 59 | return false; 60 | 61 | assert(qs instanceof wire.Question); 62 | 63 | return Boolean(CACHE[qs.toString()]); 64 | } 65 | 66 | getCache(qs) { 67 | if (!this.enabled) 68 | return null; 69 | 70 | assert(qs instanceof wire.Question); 71 | 72 | return wire.Message.fromString(CACHE[qs.toString()]); 73 | } 74 | 75 | async resolve(qs) { 76 | if (this.hasCache(qs)) 77 | return this.getCache(qs); 78 | 79 | const resolved = await super.resolve(qs); 80 | 81 | if (!resolved) 82 | return null; 83 | 84 | this.setCache(qs, resolved); 85 | return resolved; 86 | } 87 | } 88 | 89 | exports.CachedStubResolver = CachedStubResolver; 90 | 91 | exports.STUB_SERVERS = ['1.1.1.1', '1.0.0.1']; 92 | -------------------------------------------------------------------------------- /test/util/wallet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const blake2b = require('bcrypto/lib/blake2b'); 5 | const random = require('bcrypto/lib/random'); 6 | const ChainEntry = require('../../lib/blockchain/chainentry'); 7 | const Input = require('../../lib/primitives/input'); 8 | const Outpoint = require('../../lib/primitives/outpoint'); 9 | const {ZERO_HASH} = require('../../lib/protocol/consensus'); 10 | 11 | const walletUtils = exports; 12 | 13 | const REGTEST_TIME = 1580745078; 14 | 15 | walletUtils.fakeBlock = (height, prevSeed = 0, seed = prevSeed) => { 16 | assert(height >= 0); 17 | const prev = height === 0 ? ZERO_HASH : blake2b.digest(fromU32(((height - 1) ^ prevSeed) >>> 0)); 18 | const hash = blake2b.digest(fromU32((height ^ seed) >>> 0)); 19 | const root = blake2b.digest(fromU32((height | 0x80000000 ^ seed) >>> 0)); 20 | 21 | return { 22 | hash: hash, 23 | prevBlock: prev, 24 | merkleRoot: root, 25 | time: REGTEST_TIME + (height * (10 * 60)), 26 | bits: 0, 27 | nonce: 0, 28 | height: height, 29 | version: 0, 30 | witnessRoot: Buffer.alloc(32), 31 | treeRoot: Buffer.alloc(32), 32 | reservedRoot: Buffer.alloc(32), 33 | extraNonce: Buffer.alloc(24), 34 | mask: Buffer.alloc(32) 35 | }; 36 | }; 37 | 38 | walletUtils.dummyInput = () => { 39 | const hash = random.randomBytes(32); 40 | return Input.fromOutpoint(new Outpoint(hash, 0)); 41 | }; 42 | 43 | walletUtils.deterministicInput = (id) => { 44 | const hash = blake2b.digest(fromU32(id)); 45 | return Input.fromOutpoint(new Outpoint(hash, 0)); 46 | }; 47 | 48 | walletUtils.nextBlock = (wdb, prevSeed = 0, seed = prevSeed) => { 49 | return walletUtils.fakeBlock(wdb.state.height + 1, prevSeed, seed); 50 | }; 51 | 52 | walletUtils.curBlock = (wdb, prevSeed = 0, seed = prevSeed) => { 53 | return walletUtils.fakeBlock(wdb.state.height, prevSeed, seed); 54 | }; 55 | 56 | walletUtils.fakeEntry = (height, prevSeed = 0, curSeed = prevSeed) => { 57 | const cur = walletUtils.fakeBlock(height, prevSeed, curSeed); 58 | return new ChainEntry(cur);; 59 | }; 60 | 61 | walletUtils.nextEntry = (wdb, curSeed = 0, nextSeed = curSeed) => { 62 | const next = walletUtils.nextBlock(wdb, curSeed, nextSeed); 63 | return new ChainEntry(next); 64 | }; 65 | 66 | walletUtils.curEntry = (wdb, prevSeed = 0, seed = prevSeed) => { 67 | return walletUtils.fakeEntry(wdb.state.height, seed); 68 | }; 69 | 70 | function fromU32(num) { 71 | const data = Buffer.allocUnsafe(4); 72 | data.writeUInt32LE(num, 0, true); 73 | return data; 74 | } 75 | 76 | walletUtils.dumpWDB = async (wdb, prefixes) => { 77 | const data = await wdb.dump(); 78 | const filtered = {}; 79 | 80 | for (const [key, value] of Object.entries(data)) { 81 | for (const prefix of prefixes) { 82 | if (key.startsWith(prefix)) { 83 | filtered[key] = value; 84 | break; 85 | } 86 | } 87 | } 88 | 89 | return filtered; 90 | }; 91 | -------------------------------------------------------------------------------- /test/wallet-change-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('bsert'); 4 | const FullNode = require('../lib/node/fullnode'); 5 | const Address = require('../lib/primitives/address'); 6 | const {rimraf, testdir} = require('./util/common'); 7 | 8 | const path = testdir('walletchange'); 9 | 10 | const node = new FullNode({ 11 | prefix: path, 12 | memory: false, 13 | network: 'regtest', 14 | plugins: [require('../lib/wallet/plugin')] 15 | }); 16 | 17 | const {wdb} = node.require('walletdb'); 18 | 19 | let wallet, recAddr; 20 | const changeAddrs = []; 21 | const manualChangeAddrs = []; 22 | 23 | async function mineBlocks(n, addr) { 24 | addr = addr ? addr : new Address().toString('regtest'); 25 | for (let i = 0; i < n; i++) { 26 | const block = await node.miner.mineBlock(null, addr); 27 | await node.chain.add(block); 28 | } 29 | } 30 | 31 | describe('Derive and save change addresses', function() { 32 | before(async () => { 33 | await node.ensure(); 34 | await node.open(); 35 | 36 | wallet = await wdb.create(); 37 | recAddr = await wallet.receiveAddress(); 38 | }); 39 | 40 | after(async () => { 41 | await node.close(); 42 | await rimraf(path); 43 | }); 44 | 45 | it('should fund account', async () => { 46 | await mineBlocks(2, recAddr); 47 | 48 | // Wallet rescan is an effective way to ensure that 49 | // wallet and chain are synced before proceeding. 50 | await wdb.rescan(0); 51 | 52 | const aliceBal = await wallet.getBalance(0); 53 | assert(aliceBal.confirmed === 2000 * 2 * 1e6); 54 | }); 55 | 56 | it('should send 20 transactions', async () => { 57 | for (let i = 0; i < 20; i++) { 58 | const tx = await wallet.send({outputs: [{ 59 | address: Address.fromHash(Buffer.alloc(32, 1)), 60 | value: 10000 61 | }]}); 62 | 63 | for (const output of tx.outputs) { 64 | if (output.value !== 10000) 65 | changeAddrs.push(output.address); 66 | } 67 | } 68 | }); 69 | 70 | it('should have incremented changeDepth by 20', async () => { 71 | const info = await wallet.getAccount(0); 72 | assert.strictEqual(info.changeDepth, 21); 73 | assert.strictEqual(changeAddrs.length, 20); 74 | }); 75 | 76 | it('should have all change addresses saved', async () => { 77 | for (const addr of changeAddrs) { 78 | assert(await wallet.hasAddress(addr)); 79 | } 80 | }); 81 | 82 | it('should manually generate 20 change addresses', async () => { 83 | for (let i = 0; i < 20; i++) { 84 | const addr = await wallet.createChange(); 85 | manualChangeAddrs.push(addr.getAddress()); 86 | } 87 | }); 88 | 89 | it('should have incremented changeDepth by 20', async () => { 90 | const info = await wallet.getAccount(0); 91 | assert.strictEqual(info.changeDepth, 41); 92 | assert.strictEqual(manualChangeAddrs.length, 20); 93 | }); 94 | 95 | it('should have all change addresses saved', async () => { 96 | for (const addr of manualChangeAddrs) { 97 | assert(await wallet.hasAddress(addr)); 98 | } 99 | }); 100 | }); 101 | --------------------------------------------------------------------------------